条款20:宁以pass-by-reference-to-const替换pass-by-value
Prefer pass-by-reference-to-const to pass-by-value.
Slicing问题
以by reference方式传递参数,也可以避免slicing(对象切割)问题。
当一个derived class对象以by value方式进行传递,并被视为一个base class对象,base class的copy构造函数会被调用,然而:
- “造成此对象的行为像一个derived class对象”的那些特质化的特征全部被切割掉了,仅仅留下了一个base class对象。
造成这种情况的原因是,这个对象正是base class构造函数进行建立的,然而这种现象是我们一定不希望看到的。举个例子,假设我们定义一组class,用来实现一个图形窗口系统:
class Window {
public:
...
std::string name() const; //返回窗口的名称
virtual void display() const; //显示窗口和其中的内容
};
class WindowWithScrollBars : public Window {
public:
...
virtual void display() const;
};
对于所有的Window对象,都有一个名称,我们可以通过name函数获取。所有的窗口显示,我们也可以通过display函数来进行实现。
其中,display函数是一个virtual函数,这就意味着base class Window对象的显示方式和WindowWithScrollBars对象的显示方式是不同的。
而当我们希望写一个函数去打印窗口的名称,然后显示该窗口,下面的写法是错误的!
void printNameAndDisply(Window w) //不正确,参数可能会被切割
{
std::cout<<w.name();
w.display();
}
当我们调用上述的参数并向其传递一个WindowWithScrollBars对象时:
WindowWithScrollBars wwsb;
printNameAndDisplay(wwsb);
此时,参数w会被构造称为一个Window对象,因为它是pass by value的。
于是,使得wwsb“之所以是一个WindowWithScrollBars对象”的所有特征都会被切割掉,简而言之:
- 在printNameAndDisplay函数内不管是传递过来的对象时什么类型,参数w就像是一个Window对象。
因此,在printNameAndDisplay函数内调用display调用的总是Window::display,而绝不会是WindowWithScrollBars::display。
解决切割(slicing)问题的办法,就是以by reference-to-const方式传递w:
void printNameAndDisplay(const Window& w)
{
std::cout<<w.name();
w.display();
}
此时,传进来的窗口的是什么类型,w就表现出那种类型。
内置类型的情况
当我们观察C++编译器底层时,可以看到reference往往是以指针来实现的,因此:
- pass by reference通常意味传递的是指针。
根据这点,如果我们需要传递的对象属于内置类型(例如int),pass by value往往比pass by reference的效率更高。
对于这一点,也同样适用于STL的迭代器和函数对象,因为习惯上,它们都被设计为passed by value。
因为内置类型都相当的小,所以可能有人就会认为,所有小型types都可以使用pass-by-value,甚至当它们是用户自定义的class也一样,这个结论是错误的!因为对象小不代表copy构造函数的代价就不高。
有许多对象——包括大多数STL容器——内含的东西只比一个指针多一些而已,但是复制这种对象却需要承担“赋值这些指针所指的每一样东西”。因此,代价也是非常昂贵的。
即时小型对象拥有并不昂贵的copy构造函数,在效率上也可能有差距。某些编译器在对待“内置类型”和“用户自定义类型”的态度上截然不同,即使两者拥有相同的底层表述(underlying representation)。
“小型的用户自定义类型不一定通过pass-by-value”的另一个理由是:
- 作为一个用户自定义类型,其大小容易变化。
一个type目前虽然比较小,将来却可能会变得比较大,因为其内部实现可能会变化。
一般而言,我们可以认为:
- pass-by-value代价不高的唯一对象就是内置类型和STL的迭代器和函数对象。
以至于所其他其他的任何东西,都应当尽量以pass-by-reference-to-const替换pass-by-value。
最后: