分享
 
 
 

翻译:Effective C++, 3rd Edition, Item 20: 用传引用给 const(pass-by-reference-to-const)取代传值(pass-by-value)(上)

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

Item 20: 用传引用给 const(pass-by-reference-to-const)取代传值(pass-by-value)

缺省情况下,C++ 以传值方式将对象传入或传出函数(这是一个从 C 继承来的特性)。除非你特别指定其它方式,否则函数的参数就会以实际参数(actual argument)的拷贝进行初始化,而函数的调用者会收到函数返回值的一个拷贝。这个拷贝由对象的拷贝构造函数生成。这就使得传值(pass-by-value)成为一个代价不菲的操作。例如,考虑下面这个类层级结构:

class Person {

public:

Person(); // parameters omitted for simplicity

virtual ~Person(); // see Item 7 for why this is virtual

...

private:

std::string name;

std::string address;

};

class Student: public Person {

public:

Student(); // parameters again omitted

~Student();

...

private:

std::string schoolName;

std::string schoolAddress;

};

现在,考虑以下代码,在此我们调用一个函数—— validateStudent,它得到一个 Student 参数(以传值的方式),并返回它是否验证有效的结果:

bool validateStudent(Student s); // function taking a Student

// by value

Student plato; // Plato studied under Socrates

bool platoIsOK = validateStudent(plato); // call the function

当这个函数被调用时会发生什么呢?

很明显,Student 的拷贝构造函数被调用,用 plato 来初始化参数 s。同样明显的是,当 validateStudent 返回时,s 就会被销毁。所以这个函数的参数传递代价是一次 Student 的拷贝构造函数的调用和一次 Student 的析构函数的调用。

但这还不是全部。一个 Student 对象内部包含两个 string 对象,所以每次你构造一个 Student 对象的时候,你也必须构造两个 string 对象。一个 Student 对象还要从一个 Person 对象继承,所以每次你构造一个 Student 对象的时候,你也必须构造一个 Person 对象。一个 Person 对象内部又包含两个额外的 string 对象,所以每个 Person 的构造也承担着另外两个 string 的构造。最终,以传值方式传递一个 Student 对象的后果就是引起一次 Student 的拷贝构造函数的调用,一次 Person 的拷贝构造函数的调用,以及四次 string 的拷贝构造函数调用。当 Student 对象的拷贝被销毁时,每一个构造函数的调用都对应一个析构函数的调用,所以以传值方式传递一个 Student 的全部代价是六个构造函数和六个析构函数!

好了,这是正确的和值得的行为。毕竟,你希望你的全部对象都得到可靠的初始化和销毁。尽管如此,如果有一种办法可以绕过所有这些构造和析构过程,应该变得更好,这就是:传引用给 const(pass by reference-to-const):

bool validateStudent(const Student& s);

这样做非常有效:没有任何构造函数和析构函数被调用,因为没有新的对象被构造。被修改的参数声明中的 const 是非常重要的。validateStudent 的最初版本接受一个 Student 值参数,所以调用者知道它们屏蔽了函数对它们传入的 Student 的任何可能的改变;validateStudent 也只能改变它的一个拷贝。现在 Student 以引用方式传递,同时将它声明为 const 是必要的,否则调用者必然担心 validateStudent 改变了它们传入的 Student。

以传引用方式传递参数还可以避免切断问题(slicing problem)。当一个派生类对象作为一个基类对象被传递(传值方式),基类的拷贝构造函数被调用,而那些使得对象的行为像一个派生类对象的特殊特性被“切断”了。你只剩下一个纯粹的基类对象——这没什么可吃惊的,因为是一个基类的构造函数创建了它。这几乎绝不是你希望的。例如,假设你在一组实现一个图形窗口系统的类上工作:

class Window {

public:

...

std::string name() const; // return name of window

virtual void display() const; // draw window and contents

};

class WindowWithScrollBars: public Window {

public:

...

virtual void display() const;

};

所有 Window 对象都有一个名字,你能通过 name 函数得到它,而且所有的窗口都可以显示,你可一个通过调用 display 函数来做到这一点。display 为 virtual 的事实清楚地告诉你:一个纯粹的基类的 Window 对象的显示方法有可能不同于专门的 WindowWithScrollBars 对象的显示方法(参见 Item 34 和 36)。

现在,假设你想写一个函数打印出一个窗口的名字,并随后显示这个窗口。以下这个函数的写法是错误的:

void printNameAndDisplay(Window w) // incorrect! parameter

{ // may be sliced!

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。

绕过切断问题的方法就是以传引用给 const 的方式传递 w:

void printNameAndDisplay(const Window& w) // fine, parameter won't

{ // be sliced

std::cout << w.name();

w.display();

}

现在 w 将表现得像实际传入的那种窗口。

(未完待续)

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
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- 王朝網路 版權所有