条款20:宁以pass-by-reference-to-const替换pass-by-value

条款20:宁以pass-by-reference-to-const替换pass-by-value
        (Prefer pass-by-reference-to-const to pass-by-value.)

内容:
    先看一个例子,假设我们有一个Person类:
    class Person{
    public:
        Person(const string& name,const string& address)
        :name_(name),address_(address){
        };
        virtual ~Person(){}
        ...
    private:
        ...
        string name_;
        string address_;
    };
    现在有一个Student类继承Person类:
    class Student:public Person{
    public:
        Student(...){...}; //omit parameters for simplicity
        ~Student(){}
        ...
    private:
        ...
        string schoolName_;
        string schoolAddress_;
    };
    现在这里有一个函数函数要处理Person对象,其原型签名是这样:
    void queryPersonInfo(Person somebody);
    我们可以这样调用:
    ...
    Person firstPerson("michael scofield","New York");
    queryPersonInfo(firstPerson); // Label1
    Student firstStudent(...);
    queryPersonInfo(firstStudent); //Label2
    ...
    上面这段代码中由于queryPersonInfo函数传入参数是一个对象,那么参数传递中发生什么事情呢?C++默认的参数
传递方式是按值传递(pass-by-value),所谓按值传递就是函数调用过程中,产生了一个对象副本(调用该对象的copy构
造函数产生),该副本才被真正用来传递给目标函数的,函数对参数的所做的动作改变的都是对象副本而原对象没有发
生任何改变.
    如果我们用上面的参数传递方式,那么我们将有可能发生如下糟糕的事情:
    (1)如此的函数调用会带来效率问题.如label1代码中,对象副本的产生过程也是新对象local成员变量的产生过程
(name_,address_,schoolName_,schoolAddress_四个对象发生拷贝构造),哦欧,没想到吧,仅仅的一个参数传递方式付
出了如此高的代价,如果你的代码中一直都用的是这种参数传递方式,那么你的程序效率将大打折扣.
    (2)可能会发生"对象切割"(object slicing)现象.如label2代码中,函数原型中参数类型是Person,而这里你却传入
一个类型为Student对象,由于它们之间的继承关系的作用,编译器可以把firstStudent当做一个Person来处理,这样的
话,firstStudent的base class将被以pass-by-value进行传递,(1)所述将调用base class的copy构造函数创建原对象
副本传入目标函数,而firstStudent具有的derived class的那些特性将被完全切割掉,仅仅留下一个base class,oh
no,这太吓人了,这不是我想要的结果.
    为了避免上述两种情况出现,我们改变一下函数原型:
    void queryPersonInfo(const Person& thePerson);
    这种参数传递方式称之为pass-by-reference-to-const,这种方式在传递过程中不会有对象副本的拷贝构造发生,
它传过去的就是原对象本身的引用,底层实现上,引用是通过指针来实现的,也就是传递过去的是原对象的指针,而(2)
的情况也不会发生,因为引用或指针对象的参数可以引起多态性行为,也就是解决了"对象分割"引起的问题.通过下面这个
例子我们就能够更加清楚的理解这一点.
    这里我们有一个图形窗口系统Window类:
    class Window{
    public:
        ...
        std::string name()const;     //返回窗口名称
        virtual void display()const; //显示窗口和它的内容
    };
    假设这里有它的一个子类WindowWithScrollBars,它是带有滚动条的窗口系统:
    class WindowWithScrollBars:public Window{
    public:
        ...
        virtual void display()const;
    };
    这里有个函数用来打印窗口名称,然后显示该窗口,我们先用pass-by-value进行函数原型设计:
    void printNameAndDisplay(Window win){ //critical warning: object-slicing 
        std::cout<<win.name();
        w.display();
    }
    我们来调用它:
    WindowWithScrollBars windowbar;
    printNameAndDisplay(windowbar);
    上面这行代码就有可能发生对象分割(当然产生对象副本),这里传入的windowbar对象的WindowWithScrollBars
的特化信息都会被切除,那么该函数输出的窗口名称就是base class的name,不是windowbar的名称,即该函数实际调
用的display版本为子类Window::display,而不是WindowWithScrollBars::display.
    我们再用pass-by-reference-to-const进行函数原型设计:
    void printNameAndDisplay(const Window& win){
        ...//同上
    }
    调用该函数的代码也不变.这里参数传进来的对象是哪种类型,该函数就调用那个类型的display版本,这里调用
的是WindowWithScrollBars::display.
    那么我们何时使用pass-by-value方式,何时使用pass-by-reference-const方式进行参数传递呢?大多数人都采
用这样一个不成文的约定:对于内置型对象(如int),STL迭代器以及函数对象使用pass-by-value,而对于自定义类对
象则采用pass-by-reference-const的方式进行参数传递.
    好了,今天我们就讨论到这里.
   
    请记住:
    ▲ 尽量以pass-by-reference-to-const替换pass-by-value.前者通常比较高效,并可避免切割问题(slicing
problem).
    ▲ 以上规则并不适合与内置类型,以及STL的迭代器和函数对象.对它们而言,pass-by-value往往比较适合.

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值