zoukankan      html  css  js  c++  java
  • 条款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。

     

  • 相关阅读:
    access 导数据到sql server 2008
    axis2 调用.net基于https的WebService接口
    android layout 属性大全
    sqlite-manager
    android Permission 访问权限许可
    ImageSwitcher 右向左滑动的实现方式
    java 全角半角转换函数
    Delphi中使用Office中VBA的优缺点
    Delphi中控制VBA 宏
    Delphi 与 Word_VBA
  • 原文地址:https://www.cnblogs.com/stemon/p/4577293.html
Copyright © 2011-2022 走看看