您的位置:首页 > 产品设计 > UI/UE

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

2009-06-17 11:17 531 查看
条款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往往比较适合.
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐