[020]宁以pass-by-reference-to-const替换pass-by-value

前言:

我们都用过C的值传递方式,那么在C++情况下,值传递是怎样工作的呢?

比如:

int foo(int x);
int i;
foo(i);

1.程序内部先取得i的一个副本

2.将副本传递到foo函数

3.foo返回

这些副本的都是由拷贝构造函数产出的,当参数过多或者逻辑复杂时,就可能使得值传递成为费事的操作。

第一节:用pass-by-reference-to-const替换pass-by-value

我们先看类的值传递过程:

 1 class A {
 2 public:
 3     A() {cout << "Call A\n"; }
 4     A(const A& aCopy) {    // 拷贝构造函数
 5         a = aCopy.a;
 6     }
 7     virtual ~A(){}
 8 private:
 9     string a;
10 };
11
12 class B: public A {
13 public:
14     B() {cout << "call B\n"; }
15     ~B() {}
16 private:
17     string b;
18 };
19
20 void foo(B b) {
21     return;
22 }

接下来我们这么调用:

 B b1;                  // 会直接先调用A的构造函数
                        // 再调用B的构造函数
 foo(b1);               // 副本调用A的构造函数
                        // 再调用B的构造函数

上面并不是调用的所有数据,因为我们还有类的string成员!

在每次构造的时候都会调用string的构造函数,因此除了调用一次A的构造函数和一次B的构造函数,我们还额外调用了两次string的构造函数。

另外,我们还需要调用相对应的析构函数,因此上面的代码调用次数总和为:4次构造函数+4次析构函数。

简单的一个赋值,就导致这么多次函数调用,效率是问题!

方案:我们可以使用const引用来解决这个问题。在这种方式下,任何构造函数和析构函数都不需要调用。

void foo(const B& b) {
    return;
}

因为引用是直接对对象进行操作的,不存在副本之说。另外定义为const,是为了不让传递的实参被误改!

除了调用函数次数减少,使用const引用还可以带来以下的好处。

★避免对象切割问题(slicing)

用一个形象的例子来解释:

 1 class A {
 2 public:
 3     A() {cout << "Call A\n"; }
 4     A(const A& aCopy) {
 5         a = aCopy.a;
 6     }
 7     virtual ~A(){}
 8 private:
 9     string a;
10 };
11
12 class B: public A {
13 public:
14     B() {cout << "call B\n"; }
15     ~B() {}
16 private:
17     string b;
18 };
19
20 void foo1(A a) {
21     return;
22 }

如果我们这个时候将子类对象传入foo1()函数。

B b1;
foo1(b1);    // 子转父

在这个时候,b1在值传递的时候会被认为是一个基类对象,从而产生一个基类副本,A的拷贝构造函数被会调用,于是子类B中的特性会被全部切割,仅仅留下一个基类对象!

而解决切割问题的最好方法:就是使用const引用传值!

void foo1(const A& a) {  // 不再调用A的拷贝构造函数!
    return;
}

第二节:基本类型的传递    

如果我们去探寻C++编译器的底层,就会发现:引用往往以指针实现出来,因此通过指针传递通常意味着真正传递的是指针。

因此,对于内置类型而言,值传递的效率其实比引用传递的效率高些。

对于STL的迭代器和函数对象而言,这个结果也是适用的。

◆总结

1.尽量以pass-by-reference-to-const替换pass-by-value。前者通常比较高效,并可避免切割问题(slicing problem);

2.在针对内置类型以及STL迭代器和函数对象时,一般还是值传递比较高效。

时间: 2024-10-11 02:08:15

[020]宁以pass-by-reference-to-const替换pass-by-value的相关文章

对常量的引用(reference to const)的一般用途(转载)

如果是对一个常量进行引用,则编译器首先建立一个临时变量,然后将该常量的值置入临时变量中,对该引用的操作就是对该临时变量的操作.对C++常量引用可以用其它任何引用来初始化:但不能改变. 关于引用的初始化有两点值得注意: (1)当初始化值是一个左值(可以取得地址)时,没有任何问题: (2)当初始化值不是一个左值时,则只能对一个const T&(常量引用)赋值.而且这个赋值是有一个过程的: 首先将值隐式转换到类型T,然后将这个转换结果存放在一个临时对象里,最后用这个临时对象来初始化这个引用变量. 通过

《Distributed Programming With Ruby》读书笔记一Drb:Hellowold and Pass by Reference

<Distributed Programming With Ruby>Mark Bates Preface: The author was using Java and RMI(remote method invocation) as distributed interface in 2001. To solve performence problems, he found DRb. "I was already impressed with Ruby for being a ter

C++基础和STL,Effective C++笔记

C++基础 static static变量存储在静态数据区 相对于function:在函数内,变量,内存只被分配一次,多次调用值相同 相对于其他模块(.c文件):变量和函数,不能被模块外其他函数访问(private) 相对于类:类中的static变量和函数属于整个类,而不是对象 全局变量 VS 全局静态变量 若程序由一个源文件构成时,全局变量与全局静态变量没有区别. 若程序由多个源文件构成时,全局变量与全局静态变量不同:全局静态变量使得该变量成为定义该变量的源文件所独享,即:全局静态变量对组成该

Effective C++ 4.设计与声明

//条款18:让接口容易被正确使用,不易被误用 // 1.如果客户企图使用某个接口而却没有获得他所预期的行为,那么这个代码就不该通过编译. // 2.促进正确使用的方法包括接口的一致性,以及与内置类型的行为兼容. // 3.阻止误用的方法包括建立新类型.限制类型上的操作,束缚对象值,以及消除客户的资源管理责任. // 4.shared_ptr支持自定义删除器,可以方便的用于管理各种资源. //条款20:pass by reference to const 替换 pass by value //

effective c++ 笔记 (6)

//---------------------------15/04/06---------------------------- //#18 让接口容易被正确使用,不易被误用 { //  1:为了防止客户输入错误的参数,可以使用外覆类型来区别: struct Day { explicit Day(int d): val(d) {} int val; }; struct Month { explicit Month(int m): val(m) {} int val; }; struct Yea

[C++]关于接口的设计与声明--对封装性的理解

设计与声明 所谓软件设计,是"令软件做出你希望它做的事情"的步骤和方法,通常以颇为一般性的构想开始,最终十足的细节,以允许特殊接口(interface)的开发.这些接口而后必须转换为C++声明式.本文讨论对良好C++接口的设计和声明. 1. 让接口容易被正确使用,不易被误用 C++拥有许多的接口,function接口,class接口,template接口-.每一种接口实施客户与你的代码互动的手段.理想情况下,客户总是会准确的使用你的接口并获得理想的结果,而如果客户错误的使用了接口,代码

《Effective C++》资源管理:条款20-条款21

条款20:宁以pass-by-reference-to-const替换pass-by-value 在默认情况下,C++函数传递参数是继承C的方式,是值传递(pass by value).这样传递的都是实际实参的副本,这个副本是通过调用复制构造函数来创建的.有时候创建副本代价非常昂贵.例如一下继承体系 class Person{ public: Person(); virtual ~Person(); -- private: std::string name; std::string addres

effective c++18-25条款“接口设计与声明”整理

一.让接口容易被正确使用,不易被误用 接口设计的原则是,方便日后和其他用户的使用,不要把问题留给接口使用者 (1)用常规的用法调用"特别"设计的接口.所以需要尽可能的把自己的设计往常规上靠:数据对象的行为要尽可能符合内建对象(比如int)的行为:接口的名字和意义要尽可能一致(比如STL中的容器基本都有一个叫做size的返回容器大小的接口)--这样做鼓励用户去正确的看待和使用你的接口. (2)忘了处理调用接口后的遗留问题.因此不要让用户去"记得"做一些事情. 如设计一

C++类设计1(Class without pointer members)

class complex{ public: complex (double r = 0, double i = 0):re(r), im(i){} //inline complex& operator += {const complex&}; double real() const{return re;} //inline double imag() const{return im;} //inline private: double re,im; friend complex&

《Effective C++》条款20宁以pass-by-reference-to-const替换pass-by-value

<Effective C++> 条款20:宁以pass-by-reference-to-const替换pass-by-value 缺省情况下C++以by value方式传递对象至函数.除非你另外知道,否则函数参数都是以实际参数的副本为初值,而调用端所获得的亦是函数返回值的一个复件.这些复件系由copy构造函数产出,这可能使得pass-by-value成为昂贵的费时的操作. 通过pass-by-reference-to-const的传递方式效率高的多:原因是没有任何构造函数或析构函数被调用,因为