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; }; |
bool validateStudent(Student s); // function taking a Student // by value Student plato; // Plato studied under Socrates bool platoIsOK = validateStudent(plato); // call the function |
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; }; |
void printNameAndDisplay(Window w) // incorrect! parameter { // may be sliced! std::cout << w.name(); w.display(); } |
WindowWithScrollBars wwsb; printNameAndDisplay(wwsb); |
void printNameAndDisplay(const Window& w) // fine, parameter won’t { // be sliced std::cout << w.name(); w.display(); } |
现在 w 将表现得像实际传入的那种窗口。
如果你掀开编译器的盖头偷看一下,你会发现用指针实现引用是非常典型的做法,所以以引用传递某物实际上通常意味着传递一个指针。由此可以得出结论,如果你有一个内建类型的对象(例如,一个 int),以传值方式传递它常常比传引用方式更高效。那么,对于内建类型,当你需要在传值和传引用给 const 之间做一个选择时,没有道理不选择传值。同样的建议也适用于 STL 中的迭代器(iterators)和函数对象(function objects),因为,作为惯例,它们就是为传值设计的。迭代器(iterators)和函数对象(function objects)的实现有责任保证拷贝的高效并且不受切断问题的影响。(这是一个“规则如何变化,依赖于你使用 C++ 的哪一个部分”的实例。)
内建类型很小,所以有人就断定所有的小类型都是传值的上等候选者,即使它们是用户定义的。这样的推论是不可靠的。仅仅因为一个对象小,并不意味着调用它的拷贝构造函数就是廉价的。很多对象——大多数 STL 容器也在其中——容纳的和指针一样,但是拷贝这样的对象必须同时拷贝它们指向的每一样东西。那可能是非常昂贵的。
即使当一个小对象有一个廉价的拷贝构造函数,也会存在性能问题。一些编译器对内建类型和用户定义类型并不一视同仁,即使他们有同样的底层表示。例如,一些编译器拒绝将仅由一个 double 组成的对象放入一个寄存器中,即使在常规上它们非常愿意将一个纯粹的 double 放入那里。如果发生了这种事情,你以传引用方式传递这样的对象更好一些,因为编译器理所当然会将一个指针(引用的实现)放入寄存器。
小的用户定义类型不一定是传值的上等候选者的另一个原因是:作为用户定义类型,它的大小常常变化。一个现在较小的类型在将来版本中可能变得更大,因为它的内部实现可能会变化。甚至当你换了一个不同的 C++ 实现时,事情都可能会变化。例如,就在我这样写的时候,一些标准库的 string 类型的实现的大小就是另外一些实现的七倍。
通常情况下,你能合理地假设传值廉价的类型仅有内建类型及 STL 中的迭代器和函数对象类型。对其他任何类型,请遵循本 Item 的建议,并用传引用给 const 取代传值。
Things to Remember
。用传引用给 const 取代传值。典型情况下它更高效而且可以避免切断问题。
。这条规则并不适用于内建类型及 STL 中的迭代器和函数对象类型。对于它们,传值通常更合适。