条款20:宁以常量引用传递替换值传递

目录

[条款20:宁以常量引用传递替换值传递(Prefer pass-by-reference-to-const to pass-by-value)](#条款20:宁以常量引用传递替换值传递(Prefer pass-by-reference-to-const to pass-by-value))

效率问题

对象切割问题

基本原则


条款20:宁以常量引用传递替换值传递(Prefer pass-by-reference-to-const to pass-by-value)

当使用值传递时,调用者的意图是不允许修改实参;当使用另一种方法替代(即引用传递)时,也就不能修改实参,因此需要添加const修饰。

效率问题

结论1 :pass-by-reference-to-const可以避免构造和析构的调用,提高效率

场景描述

  • 基类:Person(内含两个std::string类型的成员变量)
  • 派生类:Student(内含两个std::string类型的成员变量)


实现1 (pass-by-value 低效六次 构造函数和六次析构函数(进入函数,需要创建Student对象副本;退出函数,需要销毁Student对象副本)

  • 分析++首先++ ,Student对象继承自Person对象,所以每次构造Student对象也必须构造出一个Person对象。一个Person对象又有两个string对象在其中,因此每一次Person构造动作又需承担两个string构造动作。++其次++ ,Student对象内有两个string对象,所以每次构造一个Student对象也就构造了两个string对象。++因此++,以 by value方式传递一个Student对象会导致调用一次Student copy构造函数、一次Person copy构造函数、四次string copy构造函数。当函数内的那个Student副本被销毁,每一个构造函数调用动作都需要一个对应的析构函数调用动作。
    实现2 (pass-by-reference-to-const 高效没有任何构造函数和析构函数被调用(没有任何新对象被创建)
  • 分析该函数参数声明中的 const 是++重要的(必要的)++当使用值传递时 ,调用者的意图是不允许修改实参(函数内部绝不会对传入的实参作任何改变,只能够对其副本做修改);当使用另一种方法替代(即引用传递)时,应该也不能修改实参,因此需要添加const限定(否则,调用者会忧虑函数内部是否会改变传入的实参)。

对象切割问题

结论2 :pass-by-reference-to-const可以避免对象切割( slicing )问题 (即当派生类对象作为基类对象(按值)传递时,基类的拷贝构造函数会被调用,派生类对象的特有部分/性质全被"切割",仅仅留下一个基类对象。)

场景描述:考虑一个用于表示"图形窗口系统"的类继承体系

  • name() 函数非虚函数,所有Window对象都带有一个名称,可以通过name函数获取
  • display() 函数普通虚函数 ,所有Window对象都可以显示,可以通过display函数完成。此外,display是个虚函数,表明基类对象显示方式(简易朴素)派生类对象 的**显示方式(华丽高贵)**不同。


实现1 (pass-by-value 参数可能被切割(不具有多态性)

  • 分析 :在printNameAndDisplay()函数内,无论传递过来的++实参++ ++wwsb++ 是何种类型,++形参++++w++ 就是Window 类型的对象。因此,w.display()调用的总是基类版本Window::display()(简易朴素),绝不会是派生类版本WindowWithScrollBars::display()(华丽高贵)。
  • 此处,实参的作用是切割派生类的特化信息,并构造基类对象。参数w会被构造成为一个Window对象,而造成wwsb"之所以是个WindowWithScrollBars对象"的所有特化信息都会被切除。
    实现2 (pass-by-reference-to-const 参数不会被切割(具有多态性)
  • 分析 :在printNameAndDisplay()函数内,传递进来的++实参++ ++wwsb++ 是何种类型,++形参++++w++ 就表现出那种类型。因此,w.display()调用的是派生类版本WindowWithScrollBars::display()(华丽高贵)。

基本原则

结论3:规则之改变 取决于 使用哪一部分C++(见"条款1")

结论3.1 :对于内置类型STL 的迭代器STL 的函数对象 ,使用pass-by-value比pass-by-reference-to-const的效率要高

  • STL 的迭代器:通过指针实现

  • STL 的函数对象:不含任何成员变量(仅含operator())
    结论3.2 :对于其他任何东西 ,尽量以pass-by-reference-to-const替换pass-by-value

  • Q:内置类型都相当小,是否可以认为 所有"小型对象"都是pass-by-value的合格候选人,甚至它们是"用户自定义类型"?

  • A:不是(不可靠推论),原因有如下3点("小型的用户自定义类型不必然成为pass-by-value优良候选人")

理由1小型对象 也可能拥有 昂贵的拷贝构造函数(即对象小并不意味其拷贝构造函数不昂贵)

  • 示例:许多对象(比如大多数STL容器)内含的东西只比一个指针多一些,但复制此类对象却需承担"复制这些指针所指的每一样东西",其代价非常昂贵。

理由2 :(即使小型对象拥有并不昂贵的copy构造函数,还是可能有效率上的争议。)某些编译器对待"内置类型"和"用户自定义类型"的态度截然不同,纵使两者拥有相同的底层表述。

  • 示例:对于自定义类型的对象(只含一个double成员),某些编译器也会拒绝将其放进缓存器内;而对于内置类型的double对象,编译器却很乐意那么做。当这种事发生,你更应该以by reference方式传递此等对象,因为编译器当然会将指针(references的实现体)放进缓存器内,绝无问题。

理由3"用户自定义类型"的大小容易有所变化。

  • 示例1:一个type目前虽然小,将来也许会变大,因为其内部实现可能改变。
  • 示例2:改用C++编译器也有可能改变type的大小。对于string类型,某些标准库实现版本比其他版本大7倍。
相关推荐
华科大胡子7 天前
条款03:尽可能使用const
const·effective c++·const成员函数·const修饰指针·stl迭代器
Once_day2 个月前
C++之《Effective C++》读书总结(4)
c语言·c++·effective c++
Once_day2 个月前
C++之《Effective C++》读书总结(2)
开发语言·c++·effective c++
Once_day2 个月前
C++之《Effective C++》读书总结(1)
c语言·c++·effective c++
杨筱毅5 个月前
【穿越Effective C++】条款17:以独立语句将newed对象置入智能指针——异常安全的智能指针初始化
c++·effective c++
杨筱毅5 个月前
【穿越Effective C++】条款13:以对象管理资源——RAII原则的基石
开发语言·c++·effective c++
杨筱毅5 个月前
【穿越Effective C++】条款8:别让异常逃离析构函数——C++异常安全的关键支柱
c++·effective c++
杨筱毅5 个月前
【穿越Effective C++】条款5:了解C++默默编写并调用哪些函数——编译器自动生成的秘密
c++·effective c++·1024程序员节
程序员老舅5 个月前
C++参数传递:值、指针与引用的原理与实战
c++·c/c++·值传递·引用传递·指针传递·参数传递机制