条款20:宁以pass-by-reference-to-const替换pass-by-value
本条款的要点:
1、尽量以pass-by-reference-to-const替换pass-by-value。前者更高效且可以避免切割问题。
2、这条规则并不适用于内建类型及STL中的迭代器和函数对象类型。对于它们,pass-by-value通常更合适。
缺省的情况下,C++以by-value方式传递对象至函数,或者获取函数的对象返回值。除非你另外的指定,否则函数参数都是以实际实参的副本为初值,而调用端所获得的也是函数返回值的一个副本。这些副本都是有对象的copy构造函数得到的。这可能使得pass-by-value成为昂贵的操作,同时也可能带来对象的切割问题。
效率的考量
对于pass-by-reference的效率的问题看下面class的继承体系:
class Person
{
public:
Person();
virtual ~Person();
...
private:
string name;
string address;
}; class Student:public Person
{
public:
Student();
~Student();
...
private:
string schoolName;
string schoolAddress;
};
现在考虑下面代码,其中调用函数validateStudent,后者需要一个Student实参(pass-by-value)并返回它是否有效:
bool validateStudent(Student s);//声明一个函数,函数以by value方式接受学生
Student plato; //类的对象plato
bool stuIsOK=validateStudent(plato);
当上述函数被调用时,发生了什么事?
很明显,Student的拷贝构造函数被调用,用plato来初始化参数s。同样明显的是,当 validateStudent返回时,s就会被销毁。所以这个函数的参数传递代价是一次Student的拷贝构造函数的调用和一次Student的析构函数的调用。
但这还不是全部。Student对象内部包含两个string对象,Student对象还要从一个 Person对象继承,Person对象内部又包含两个额外的string对象。最终,以传值方式传递一个Student对象的后果就是引起一次Student的拷贝构造函数的调用,一次Person的拷贝构造函数的调用,以及四次string的拷贝构造函数调用。当Student对象的拷贝被销毁时,每一个构造函数的调用都对应一个析构函数的调用,所以以传值方式传递一个Student的全部代价是六个构造函数和六个析构函数!
这是正确和值得的行为。毕竟,你希望全部对象都得到可靠的初始化和销毁。尽管如此,pass by reference-to-const方式会更好:
bool validateStudent(const Student& s);
这样做非常有效:没有任何构造函数和析构函数被调用,因为没有新的对象被构造。
修改后参数声明中的const是非常重要的,原先validateStudent以by-value方式接受一个Student参数,所以调用者知道函数绝不会对它们传入的Student做任何改变,validateStudent只能改变它的副本。现在Student以引用方式传递,同时将它声明为const是必要的,否则调用者必然担心validateStudent改变了它们传入的Student。
const Student& s表示不能通过reference s修改传进来的Student对象(并不是说这个传进来的Student对象是read-only的)。类似于const int *a;这样的定义。
对象切割问题
以传引用方式传递参数还可以避免切割问题(slicing problem)。当一个派生类对象作为一个基类对象被传递(传值方式),基类的拷贝构造函数被调用,而那些使得对象行为像一个派生类对象的特化性质被“切断”了,只剩下一个纯粹的基类对象例如,假设你在一组实现一个图形窗口系统的类上工作:
class Window {
public:
...
std::string name() const; // 返回窗口名称
virtual void display() const; // 显示窗口及其内容
}; class WindowWithScrollBars: public Window {
public:
...
virtual void display() const;
};
所有Window对象都有一个名字(name函数),而且所有的窗口都可以显示(display函数)。display为 virtual的事实清楚地告诉你:基类的Window对象的显示方法有可能不同于专门的WindowWithScrollBars对象的显示方法。现在,假设你想写一个函数打印出一个窗口的名字,并随后显示这个窗口。以下是错误示范:
void printNameAndDisplay(Window w) //incorrect! 参数可能被切割
{
std::cout << w.name();
w.display();
}
考虑当你用一个 WindowWithScrollBars 对象调用这个函数时会发生什么:
WindowWithScrollBars wwsb;
printNameAndDisplay(wwsb);
参数w将被作为一个Window对象构造——它是被传值的,而且使wwsb表现得像一个 WindowWithScrollBars对象的特殊信息都被切割了。在printNameAndDisplay中,全然不顾传递给函数的那个对象的类型,w将始终表现得像一个Window 类的对象(因为其类型是Window)。因此在printNameAndDisplay中调用display将总是调用 Window::display,绝不会是WindowWithScrollBars::display。其实就是派生类对象被强制转换成了基类对象。
绕过切割问题的方法就是以passby reference-to-const方式传递w:
void printNameAndDisplay(const Window& w)
{ // 参数不会被切割
std::cout << w.name();
w.display();
}
现在传进来的窗口是什么类型,w就表现出那种类型
pass-by-value和pass-by-reference-to-const
小对象该pass-by-value还是pass-by-reference-to-const:
(1)一个对象小,并不意味着调用它的拷贝构造函数就是廉价的。很多对象(包括大多数STL容器)内含的东西只比一个指针多一些,但是拷贝这样的对象必须同时拷贝它们指向的每一样东西,那将非常昂贵。即使当小对象有一个廉价的拷贝构造函数,也会存在性能问题。一些编译器对内置类型和用户自定义类型并不一视同仁,即使他们有同样的底层表示。例如,一些编译器拒绝将仅由一个double组成的对象放入一个寄存器(reg)中,即使通常它们非常愿意将一个纯粹的double 放入那里。当这种事发生,你以传引用方式传递这样的对象更好一些,因为编译器理所当然会将一个指针(引用的实现)放入寄存器。
(2)小的用户定义类型不一定是传值的上等候选者的另一个原因是:作为用户定义类型,它的大小常常变化,因为将来可能会变的很大。
结论是:小对象也尽量pass-by-reference-to-const
用指针实现引用是非常典型的做法,所以pass by reference实际上通常意味着传递一个指针。
由此可以得出结论,如果你有一个内置类型对象(一个int),以传值方式传递它常常比传引用方式更高效;同样的建议也适用于 STL 中的迭代器和函数对象。
因为内置数据类型的参数不存在构造、析构的过程,而复制也非常快,“值传递”和“引用传递”的效率几乎相当。
通常情况下,你能合理地假设传值廉价的类型仅有内置类型及STL中的迭代器和函数对象。对其他任何类型,请尽量以pass-by-reference-to-const替换pass-by-value。
条款20:宁以pass-by-reference-to-const替换pass-by-value的更多相关文章
- [GeekBand ] 利用 pass by reference -to -const 编写高效规范的 c++代码
本文参考资料 : GeekBand 侯捷老师,学习笔记 Effective C ++ 侯捷译 条款20 开发环境采用:VS2013版本 首先:分析值传递的缺点 (一) class Person{ p ...
- Effective C++ -----条款20:宁以pass-by-reference-to-const替换pass-by-value Prefer pass-by-reference-to-const to pass-by-value
尽量以pass-by-reference-to-const替换pass-by-value.前者通常比较高校,并可避免切割问题(slicing problem). 以上规则并不适用于内置类型,以及STL ...
- Effective C++ 条款 50:了解new和delete的合理替换时机
(一) 为什么有人想要替换operator new 和 operator delete呢?三个常见的理由: (1)用来检測运用上的错误. (2)为了强化效果. (3)为了收集使用上的统计数据. (二) ...
- Effective C++ -----条款50:了解new 和delete 的合理替换时机
有许多理由需要写个自定的new 和delete ,包括改善效能.对heap 运用错误进行调试.收集heap 使用信息.
- Effective C++ -----条款10: 令operator=返回一个reference to *this
比如: Widget& operator=(const Widget& rhs) { ... return* this; } 令赋值(assignment)操作符返回一个referen ...
- 《MORE EFFECTIVE C++》条款20 条款21
条款20 协助编译器实现返回值优化 当重载运算符的时候,比如+ - * / 这类运算符,该函数返回的值一定是个右值(即不能是引用),那么执行一次运算的开销可能会在临时对象上调用多次构造函数和析构函数, ...
- EC读书笔记系列之11:条款20、21
条款20 宁以pass-by-reference-to-const替换pass-by-value 记住: ★尽量以pass-by-reference-to-const替换pass-by-value.前 ...
- [More Effective C++]条款22有关返回值优化的验证结果
(这里的验证结果是针对返回值优化的,其实和条款22本身所说的,考虑以操作符复合形式(op=)取代其独身形式(op),关系不大.书生注) 在[More Effective C++]条款22的最后,在返回 ...
- More Effective C++ 条款0,1
More Effective C++ 条款0,1 条款0 关于编译器 不同的编译器支持C++的特性能力不同.有些编译器不支持bool类型,此时可用 enum bool{false, true};枚举类 ...
随机推荐
- 深度探索QT窗口系统(五篇)
窗口作为界面编程中最重要的部分,没有窗口就没有界面,是窗口让我们摆脱了DOS时代,按钮是一个窗口,文本框是一个窗口,标签页是一个窗口,...一个窗口可以由多个窗口组成,每天我们都在与窗口打交道,当你打 ...
- Delphi中Android运行和JNI交互分析
Androidapi.JNIBridge负责和JNI交互.,既然要交互,那么首先就是需要获得JNI的运行环境,Android本身内置的就有一个Java(Dalvik)虚拟机.所以这个第一步就肯定是要这 ...
- java.lang.RuntimeException: Unable to start activity ComponentInfo
异常:java.lang.RuntimeException: Unable to start activity ComponentInfo{com.william/com.william.Result ...
- poj 1684 Lazy Math Instructor(字符串)
题目链接:http://poj.org/problem?id=1686 思路分析:该问题为表达式求值问题,对于字母使用浮点数替换即可,因为输入中的数字只能是单个digit. 代码如下: #includ ...
- [置顶] jsp中c标签的使用
jsp中c标签的使用 核心标签库 它是JSTL中的核心库,为日常任务提供通用支持,如显示和设置变量.重复使用一组项目.测试条件和其他操作(如导入和重定向Web内容).Core标签按功能可分为4种类型: ...
- 模式匹配-KMP算法
/***字符串匹配算法***/ #include<cstring> #include<iostream> using namespace std; #define OK 1 # ...
- XML DOM 节点
来自:w3cschool菜鸟教程 在 DOM 中,XML 文档中的每个成分都是一个节点. DOM 节点 根据 DOM,XML 文档中的每个成分都是一个节点. DOM 是这样规定的: 整个文档是一个文档 ...
- SMACSS:一个关于CSS的最佳实践-1.Overview
什么是SMACSS? SMACSS(发音"smacks"),全称Scalable and Modular Architecture for CSS.顾名思义,SMACSS是一个可扩 ...
- ASPxGridView-如何在客户端缓存数据
有时候我们可以直接从后台生成一些值缓存到客户端,在用到的时候无需在进行callback进行取值,减少和服务器的交互.下面的例子缓存列"title_id"和"title&q ...
- Android应用开发基础篇(11)-----ViewFlipper
链接地址:http://www.cnblogs.com/lknlfy/archive/2012/03/01/2376067.html 一.概述 ViewFlipper这个部件是用来实现多页显示的,多页 ...