条款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};枚举类 ...
随机推荐
- MFG 常用英文单字
Semiconductor 导体.绝缘体和半导体主要依据导电系数的大小,决定了电子的移动速度. 导体:金.银.铜.铁.人.水……导电系数大,传导容易 绝缘体:塑料.木头.皮革.纸……导电系数小.传导不 ...
- HDU 4366 Successor(树链剖分+zkw线段树+扫描线)
[题目链接] http://acm.hdu.edu.cn/showproblem.php?pid=4366 [题目大意] 有一个公司,每个员工都有一个上司,所有的人呈树状关系,现在给出每个人的忠诚值和 ...
- c++实现将表达式转换为逆波兰表达式
https://github.com/Lanying0/lintcode 所属: 数据结构->线性结构->栈 问题: 给定一个表达式字符串数组,返回该表达式的逆波兰表达式(即去掉括号). ...
- Is it possible to implement a Firebug-like “inspect element” DOM element highlighter with client-side JavaScript?
Is it possible to implement a Firebug-like "inspect element" DOM element highlighter with ...
- ios中的GCD
前面我们说了block中提到它用于多线程,而gcd则是其用于多线程的典型.gcd其全称(Grand Central Dispatch) 那到底什么叫gcd,官方的解释如下: Grand Central ...
- Android Fragment 嵌套使用报错
在新的SDK每次创建activity时,会自己主动生成 <pre name="code" class="java">public static c ...
- offsetParent 到底是哪一个?
前言 温故而知新.遇到offsetParent这个知识点,发现书上讲的不够详细.于是看了看豪情的博客,发现讲得很具体,收藏一下. 正文 不同情况 没有已定位的父节点,且自身position:relat ...
- UVa1585 Score
#include <stdio.h> int main(){ int T, O, score; char str[81], *p; scanf("%d" ...
- MSSQL2005 修改数据库的排序规则
1.修改数据库排序规则ALTER DATABASE [DataBaseName] COLLATE Chinese_PRC_CI_AS ; 2.修改表中列的排序规则 如果下列其中之一当前正在引用一个列, ...
- Hibernate的搭建及使用
1.创建普通的java项目. 因为Hibernate是一个轻量级的框架,不像servlet,还必须需要tomcat的支持,Hibernate只要jdk支持即可. 2.引入jar包. 可以在项目中直接引 ...