《Effective C++》学习笔记(条款20:宁以 pass-by-reference-to-const 替换 pass-by-value)

最近开始看《Effective C++》,为了方便以后回顾,特意做了笔记。若本人对书中的知识点理解有误的话,望请指正!!!

通常情况下,C++是以值传递(pass-by-value)的方式传递对象至函数,除非你另外指定,否则函数参数都是以实参的复件为初值,而调用函数返回的对象也是函数返回值的一个复件。这些复件是由对象的拷贝构造函数产出的,这使得值传递的成本很高(费时)。

class Person{
public:
    Person();
    virtual ~Person();  //见条款7为什么使用虚函数
    ....
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 对象会导致:StudentPerson 的 拷贝构造函数 各调用一次, 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);  //调用的永远是Window::display()

参数 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

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值