调用时,发生了什么?
这显然是一个pass-by-value的函数,也就意味着一定会出现copy构造函数,对于此函数而言,参数的传递成本是“一次student copy构造函数调用,加上一次student析构函数调用”。不仅如此,student还继承于person,所以还有一次person构造函数和person析构函数,以及student里面的两个string对象,和person里面的两个string对象,总而言之,总体成本就是“六次构造函数和六次析构函数!”多么可怕的开销!
问题解决
解决这个问题非常的简单。只要使用pass by reference to const就可以了。因为by reference不会导致构造函数和析构函数的使用,节省了大量开销,同时因为是const,也保证了参数不会再函数内被更改。
bool checkStudent(const Student &s);
问题产生2
pass-by-value还会导致对象切割问题(slicing)。当一个dereived class对象以by value方式传递并被视为一个base class对象时,bass class的copy构造函数就会被调用,而“造成此对象的行为像个derived class对象”的那些特化性质全部被切割掉,只剩下base class对象。这并不奇怪。
class Window {
public:
...
std::string name() const;
virtual void display() const;
};
class SpecialWindow {
public:
..
virtual void display() const;
};
....
// in main:
void print(Window w) {
cout << w.name();
w.display();
}
当你把一个SpecialWindow对象传递给void print(Window w)函数时,就像前文所说的,会使得SpecialWindow的特化性质全部被切割掉,于是乎,你本想着输出SpecialWindow的特别内容结果只输出了Window内容。
问题解决2
解决这个问题仍然是使用reference。由此来引发动态绑定,从而使用SpecialWindow的display。
void print(const Window& w) {
cout << w.name();
w.display();
}
总结
窥视C++编译器的底层就会发现,实际上reference就是以指针实现出来了,pass by reference通常意味着真正传递的是指针。因此,如果你有个对象属于内置类型(如int),pass-by-value通常来说效率会更好。这对于STL的迭代器和函数对象同样适用。因为习惯上他们都是设计为pass-by-value。迭代器和函数对象的实践者都有责任看看他们是否高效且不受切割问题。
有人认为,所有小型type对象都应该适用pass-by-value,甚至对于用户定义的class。实际上是不准确的。第一,对象小,并不意味着他的copy构造函数开销小;2)即使是小型对象并不拥有昂贵的copy构造函数,也可能存在效率上的问题,例如某些编译器不愿意把只由一个double组成的对象放进缓存器,但如果你使用reference,编译器一定会把指针(就是reference的实现体)放进缓存器。3)作为用户自定义类型,其大小是很容易被改变的。随着不断的使用,对象可能会越来越大。
一般而言,合理假设“pass-by-value更合适”的唯一对象就是内置类型和STL的迭代器和函数对象,其他的最好还是使用by reference。
4. 必须返回对象时,别妄想返回其reference
前面我们讨论了pass-by-reference可以提高效率,于是乎,有的人就开始坚定地使用reference,甚至开始传递一些refereence指向其实并不存在的对象。
问题产生
此问题产生的理由非常的简单,就是作者希望可以节省开销提高效率。并因此而产生大量的错误。
class Rational {
public:
Rational(int num1 = 0, int num2 = 1);
...
private:
int n1, n2;
friend Rational& operator*(const Rational& lhs, const Rational& rhs);
operator*试图返回一个引用,并为此寻找合乎逻辑的实现代码。
尝试1:直接返回
Rational& operator*(const Rational& lhs, const Rational& rhs) {
Rational result(lhs.n1 * rhs.n1, lhs.n2 * rhs.n2);
return result;
}
问题显然。因为result是一个on the stack对象,在作用域结束后,对象就被销毁,于是返回了一个没有指向的reference。尝试失败!
尝试2:返回on the heap对象
Rational& operator*(const Rational& lhs, const Rational& rhs) {
Rational* result = new Rational(lhs.n1 * rhs.n1, lhs.n2 * rhs.n2);
return *result;
}
此代码乍看起来似乎没什么问题,但其实隐含杀机。你在函数中动态申请了一块内存放这个变量,这也就意味着你必须管理这块资源(见前文:资源管理)。然而管理这块资源几乎不可能,因为你不可能希望在main函数里一直有一个变量在守着这块资源并且及时的delete掉。而且当大量使用*操作符时,管理大量的资源根本不可能!就算你有这样的毅力这么管理,也不可能希望有用户愿意做这样的体力活。
尝试3:使用static变量
Rational& operator*(const Rational& lhs, const Rational& rhs) {
static Rational result(lhs.n1 * rhs.n1, lhs.n2 * rhs.n2);
return result;
}
这代码乍看起好像又要成功了?!其实并没有。问题出现的十分隐蔽:
bool operator == (const Rational& lhs, const Rational& rhs);
if ((a*b) == (c*d)) {
...
} else {
...
}
问题就出在等号操作,等号永远会成立!因为,在operator == 被调用前,已有两个操作符被调用,每一个都返回操作函数内部的static对象,而这两个对象实际上就是一个对象!(对于调用端来说,确实如此!)于是乎,你根本就没有完成*操作符所应该具备的功能。
问题解决
问题的解决就是,别挣扎了!使用pass-by-value吧。不就是一点构造函数和析构函数的开销嘛。比起大量的错误和内存的管理。这点开销还是很划算的。
class Rational {
public:
Rational(int num1 = 0, int num2 = 1);
...
private:
int n1, n2;
friend Rational operator*(const Rational& lhs, cons