• [Effective C++ --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
    "; }
     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
    "; }
    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
    "; }
     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
    "; }
    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迭代器和函数对象时,一般还是值传递比较高效。

  • 相关阅读:
    Lotus iNotes 用户启用标识符保险库
    Domino NSD日志诊断/分析
    从 Domino 7.x 升级到 Domino 8.0.1 后服务器性能下降
    Domino服务器命令表
    源码:使用LotusScript发送mime格式邮件
    构架Domino CA中心之一
    如何在DNS中增加SPF记录
    构架Domino CA中心之二
    在Ubuntu 8.04上安装Domino R8.02
    内存陷阱 驯服C++中的野指针 沧海
  • 原文地址:https://www.cnblogs.com/hustcser/p/4165364.html
Copyright © 2020-2023  润新知