11、条款20:宁以pass-by-reference-to-const 替换pass-by-value
-
pass-by-value
会带来构造/析构的开销。此时形参是实参的副本。
-
passbyreference-to-const
没有任何构造函数或析构函数被调用,因为没有任何新对象被创建。
-
by reference方式传递参数也可以避免slicing(对象切割)问题。
当一个derived class对象以by value方式传递并被视为一个base class对象,base class的copy构造函数会被调用,而"造成此对象的行为像个derived class对象"的那些特化性质全被切割掉了,仅仅留下一个base class对象。
在printNameAndDisplay函数内不论传递过来的对象原本是什么类型,参数w就像一个Window对象(因为其类型是Window)。因此在printNameAndDisplay内调用display调用的总是Window::display,绝不会是WindowWithScrollBars::display。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
class
Window
{
public
:
std::string name()
const
;
//返回窗口名称
virtual
void
display()
const
;
//显示窗口和其内容
};
class
WindowWithScrollBars:
public
Window
{
public
:
virtual
void
display(}
const
;
};
void
printNameAndDisplay(Window w)
//不正确!参数可能被切割。
{
std::cout << w.name();
w.display();
}
|
解决切割(slicing) 问题的办法,就是以by reference-to-const的方式传递w:
1
2
3
4
5
|
void
printNameAndDisplay(
const
Window &w)
//很好,参数不会被切割。
{
std::cout << w.name();
w.display();
}
|
现在,传进来的窗口是什么类型,w就表现出那种类型。
-
尽量以pass-by-reference-to-const替换pass-by-value。前者通常比较高效,并可避免切割问题(slicingproblem) 。
-
以上规则并不适用于内置类型,以及STL 的迭代器和函数对象。对它们而言,pass-by-value往往比较适当。
12、条款21: 必须返回对象时,别妄想返回真reference
(译注:这里我补充说明:两次operator*调用的确各自改变了static Rational对象值,但由于它们返回的都是reference,因此调用端看到的永远是static Rational对象的"现值"。)
一个"必须返回新对象"的函数的正确写法是:就让那个函数返回一个新对象呗。
对Rational的operator*而言意味以下写法(或其他本质上等价的代码):
1
2
3
4
|
inlineconstRational operator* (constRational& lhs, constRational& rhs)
{
return
Rational(lhs.n* rhs.n, lhs.d * rhs.d);
}
|
13、条款22: 将成员变量声明为private
14、条款43: 学习处理模板化墓类内的名称
正如注释所言,当base class被指定为MsgSender<CompanyZ> 时这段代码不合法,因为那个class并未提供sendClear函数!那就是为什么C++拒绝这个调用的原因:它知道base class templates有可能被特化,而那个特化版本可能不提供和一般性template相同的接口。因此它往往拒绝在templatized base classes(模板化基类,本例的MsgSender<Company>)内寻找继承而来的名称(本例的SendClear)。就某种意义而言,当我们从ObjectOrientedC++跨进TemplateC++(见条款1),继承就不像以前那般畅行无阻了。
为了重头来过,我们必须有某种办法令C++ "不进入templatized base classes观察" 的行为失效。有三个办法
-
是在base class 函数调用动作之前加上"this->"
this->sendClear(info);
-
使用using声明式
using MsgSender<Company>::sendClear; //告诉编译器,请它假设sendClear位于baseclass内。
void sendClearMsg(const Msglnfo& info)
{
sendClear(info); //OK, 假设sendClear将被继承下来。
}
-
明白指出被调用的函数位于base class内
void sendClearMsg(const Msglnfo& info)
{
MsgSender<Company>::sendClear(info); //OK, 假设sendClear将被继承下来。
}
但这往往是最不让人满意的→个解法,因为如果被调用的是virtual函数,上述的明确资格修饰(explicitqualification)会关闭"virtual绑定行为"。
不推荐使用第三种方式。
根本而言,本条款探讨的是,面对"指涉base class members"之无效references,编译器的诊断时间可能发生在早期(当解析derived class template的定义式时),也可能发生在晚期(当那些templates被特定之template实参具现化时)。C++的政策是宁愿较早诊断,这就是为什么"当base classes从templates中被具现化时"它假设它对那些base classes的内容毫无所悉的缘故。
-
可在derived class templates内通过"this->" 指涉base class templates内的成员名称,或藉由一个明白写出的"base class资格修饰符"完成。