分享
 
 
 

Effective C++ 2e Item22

王朝c/c++·作者佚名  2006-01-08
窄屏简体版  字體: |||超大  

条款22: 尽量用“传引用”而不用“传值”

C语言中,什么都是通过传值来实现的,C++继承了这一传统并将它作为默认方式。除非明确指定,函数的形参总是通过“实参的拷贝”来初始化的,函数的调用者得到的也是函数返回值的拷贝。

正如我在本书的导言中所指出的,“通过值来传递一个对象”的具体含义是由这个对象的类的拷贝构造函数定义的。这使得传值成为一种非常昂贵的操作。例如,看下面这个(只是假想的)类的结构:

class Person {

public:

Person(); // 为简化,省略参数

//

~Person();

...

private:

string name, address;

};

class Student: public Person {

public:

Student(); // 为简化,省略参数

//

~Student();

...

private:

string schoolName, schoolAddress;

};

现在定义一个简单的函数returnStudent,它取一个Student参数(通过值)然后立即返回它(也通过值)。定义完后,调用这个函数:

Student returnStudent(Student s) { return s; }

Student plato; // Plato(柏拉图)在

// Socrates(苏格拉底)门下学习

returnStudent(plato); // 调用returnStudent

这个看起来无关痛痒的函数调用过程,其内部究竟发生了些什么呢?

简单地说就是:首先,调用了Student的拷贝构造函数用以将s初始化为plato;然后再次调用Student的拷贝构造函数用以将函数返回值对象初始化为s;接着,s的析构函数被调用;最后,returnStudent返回值对象的析构函数被调用。所以,这个什么也没做的函数的成本是两个Student的拷贝构造函数加上两个Student析构函数。

但没完,还有!Student对象中有两个string对象,所以每次构造一个Student对象时必须也要构造两个string对象。Student对象还是从Person对象继承而来的,所以每次构造一个Student对象时也必须构造一个Person对象。一个Person对象内部有另外两个string对象,所以每个Person的构造也必然伴随另两个string的构造。所以,通过值来传递一个Student对象最终导致调用了一个Student拷贝构造函数,一个Person拷贝构造函数,四个string拷贝构造函数。当Student对象被摧毁时,每个构造函数对应一个析构函数的调用。所以,通过值来传递一个Student对象的最终开销是六个构造函数和六个析构函数。因为returnStudent函数使用了两次传值(一次对参数,一次对返回值),这个函数总共调用了十二个构造函数和十二个析构函数!

在C++编译器的设计者眼里,这是最糟糕的情况。编译器可以用来消除一些对拷贝构造函数的调用(C++标准——见条款50——描述了具体在哪些条件下编译器可以执行这类的优化工作,条款M20给出了例子)。一些编译器也这样做了。但在不是所有编译器都普遍这么做的情况下,一定要对通过值来传递对象所造成的开销有所警惕。

为避免这种潜在的昂贵的开销,就不要通过值来传递对象,而要通过引用:

const Student& returnStudent(const Student& s)

{ return s; }

这会非常高效:没有构造函数或析构函数被调用,因为没有新的对象被创建。

通过引用来传递参数还有另外一个优点:它避免了所谓的“切割问题(slicing problem)”。当一个派生类的对象作为基类对象被传递时,它(派生类对象)的作为派生类所具有的行为特性会被“切割”掉,从而变成了一个简单的基类对象。这往往不是你所想要的。例如,假设设计这么一套实现图形窗口系统的类:

class Window {

public:

string name() const; // 返回窗口名

virtual void display() const; // 绘制窗口内容

};

class WindowWithScrollBars: public Window {

public:

virtual void display() const;

};

每个Window对象都有一个名字,可以通过name函数得到;每个窗口都可以被显示,着可以通过调用display函数实现。display声明为virtual意味着一个简单的Window基类对象被显示的方式往往和价格昂贵的WindowWithScrollBars对象被显示的方式不同(见条款36,37,M33)。

现在假设写一个函数来打印窗口的名字然后显示这个窗口。下面是一个用错误的方法写出来的函数:

// 一个受“切割问题”困扰的函数

void printNameAndDisplay(Window w)

{

cout << w.name();

w.display();

}

想象当用一个WindowWithScrollBars对象来调用这个函数时将发生什么:

WindowWithScrollBars wwsb;

printNameAndDisplay(wwsb);

参数w将会作为一个Windows对象而被创建(它是通过值来传递的,记得吗?),所有wwsb所具有的作为WindowWithScrollBars对象的行为特性都被“切割”掉了。printNameAndDisplay内部,w的行为就象是一个类Window的对象(因为它本身就是一个Window的对象),而不管当初传到函数的对象类型是什么。尤其是,printNameAndDisplay内部对display的调用总是Window::display,而不是WindowWithScrollBars::display。

解决切割问题的方法是通过引用来传递w:

// 一个不受“切割问题”困扰的函数

void printNameAndDisplay(const Window& w)

{

cout << w.name();

w.display();

}

现在w的行为就和传到函数的真实类型一致了。为了强调w虽然通过引用传递但在函数内部不能修改,就要采纳条款21的建议将它声明为const。

传递引用是个很好的做法,但它会导致自身的复杂性,最大的一个问题就是别名问题,这在条款17进行了讨论。另外,更重要的是,有时不能用引用来传递对象,参见条款23。最后要说的是,引用几乎都是通过指针来实现的,所以通过引用传递对象实际上是传递指针。因此,如果是一个很小的对象——例如int——传值实际上会比传引用更高效。

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有