最近开始看《Effective C++》,为了方便以后回顾,特意做了笔记。若本人对书中的知识点理解有误的话,望请指正!!!
通常情况下,C++是以值传递(pass-by-value)的方式传递对象至函数,除非你另外指定,否在函数参数都是以实参的复件为初值,而调用函数返回的对象也是函数返回值的一个复件。这些复件是由对象的拷贝构造函数产出的,这使得值传递的成本很高(费时)。
class Person{
public:
Person();
virtual ~Person();
....
private:
string name;
string address;
};
class Student : public Person{
public:
Student();
virtual ~Student();
...
private:
string schoolName;
string schoolAddress;
};
bool validateStudent(Student s);
int main()
{
Student stu;
bool platoOK = validateStudent(stu);
return 0;
}
当上述代码中的 validateStudent(stu) 函数被调用时,Student 的拷贝构造函数会被调用,以 stu 为参数将 s 初始化;而函数结束后,当 validateStudent() 返回 ,s 会被销毁。因此,对函数而言,参数的传递成本时“一次 Student 拷贝构造函数调用,加上一次 Student 析构函数调用”。
但这还不是全部,Student 对象内还有两个 string 对象,所有每次构造一个 Student 对象也就构造了两个 string 对象。此外 Student 继承自 Person ,所以每次构造 Student 对象也必须构造出一个 Person 对象,而 Person 对象中又有两个 string 对象,因此每构造一个Person 对象又需要构造两个 string 对象。因此,以值传递方式传递一个 Student 对象会导致:Student 和 Person 的拷贝构造函数各调用一次, string 的拷贝构造函数调用了四次。当函数结束时,又需要释放 Student 对象,每一个构造函数调用都对应了一个析构函数的调用。因此,最终成本是:六次拷贝构造函数和六次析构函数。
我们得保证函数的参数能够安全地初始化和销毁,但同样希望避免这些不必要的开销,那么就得使用常量引用传递(pass-by-reference-to-const)。
bool validateStudent(const Student& s);
使用引用传递表示在实参本身上进行读写,从而不用进行拷贝复件,那么拷贝构造函数和析构函数的调用也就省略了。若我们不想实参本身被修改,就需要加上 const 关键字,意思是这个实参是只读的。
引用传递传递参数可以避免对象切割问题。当一个函数的传入参数是基类,而你以值传递的方式传入一个派生类,参数初始化调用基类的拷贝构造函数,派生类派生出来的特性全部被“切割”掉了,仅留下一个基类对象。
class Window{
public:
...
std::string name() const;
virtual void display() const;
};
class WindowWithScrollBar : public Window{
public:
...
virtual void display() const;
};
display() 是虚函数,这就意味着两个类对于这个函数有不同的实现。现在你要实现一个函数:先打印出窗口的名字,然后显示窗口。下面是错误示范:
void printNameAndDisplay(Window w){
std::cout<<w.name();
w.display();
}
当你调用上述函数并传给它一个 WindowWithScrollBar 对象,会发生什么呢?
WindowWithScrollBar wwsb;
printNameAndDisplay(wwsb);
参数 w 会被构造成一个 Window 对象,因为该函数是值传递的,所以导致了 wwsb 之所以是 WindowWithScrollBar 对象的所有特性都被切割掉了。在 printNameAndDisplay() 内不论传递过来的对象原本是什么类型,参数 w 永远只是 Window 对象,因此在函数内调用 display() 调用的总是 Window::display() ,绝不会是 WindowWithScrollBar::display() 。
解决切割问题的方法就是使用引用传递。传进来的窗口是什么类型,w 就表现为那种类型。
void printNameAndDisplay(const Window& w){
std::cout<<w.name();
w.display();
}
在C++编译器的底层,引用是用指针来实现的,即引用传递的本指是指针传递。因此对于内置类型来说,值传递往往比引用传递的效率高。STL中的迭代器和函数对象也适合使用值传递,因为它们是根据值传递效率高的规则来设计的,并且它们不受切割问题的影响。
选择值传递还是引用传递,取决于你使用哪一部分的C++(见条款01)。
选择值传递还是引用传递与类型的大小无关。
Note:
- 尽量以引用传递(若想参数只读,加 const)替换值传递,前者通常比较高效,并可避免切个问题
- 以上规则并不适用于内置类型以及STL的迭代器和函数对象,对它们而言,值传递更高效
条款21:必须返回对象时,别妄想返回其reference
|