Item 20:传递常量引用比传值更好

传引用更加高效

缺省情况下,C++ 以传值方式将对象传入或传出函数。除非你特别指定其它方式,否则函数的参数就会以实际参数的拷贝进行初始化,而函数的调用者会收到函数返回值的一个拷贝。这个拷贝由对象的拷贝构造函数生成。这就使得传值成为一个代价不菲的操作。

例如,考虑下面这个类层级结构:

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
virtual ~Student();
...

private:
std::string schoolName;
std::string schoolAddress;
};

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

bool validateStudent(Student s);  
Student plato;       
bool platoIsOK = validateStudent(plato);  

在调用 validateStudent 时进行了6个函数调用:

  • Person 的拷贝构造函数。
  • Student的拷贝构造函数。
  • name 的拷贝构造函数。
  • address 的拷贝构造函数。
  • schoolName 的拷贝构造函数。
  • schoolAddress 的拷贝构造函数。

当 Student 对象的拷贝被销毁时,每一个构造函数的调用都对应一个析构函数的调用,所以以传值方式传递一个 Student 的全部代价是六个构造函数和六个析构函数!

解决办法便是传递常量引用:

bool validateStudent(const Student& s);
  • 以引用的方式传递,不会构造新的对象,避免了上述例子中6个构造函数的调用。
  • const 也是必须的:传值的方式保证了该函数调用不会改变原来的 Student, 而传递引用后为了达到同样的效果,需要使用 const 声明来声明这一点,让编译器去进行检查!

传值造成的截断问题

将传值改为传引用还可以有效地避免截断问题:当一个派生类对象作为一个基类对象被传递(传值方式),基类的拷贝构造函数被调用,而那些使得对象的行为像一个派生类对象的特殊特性被“切断”了。你只剩下一个纯粹的基类对象。

比如一个 Window 父类派生了子类 WindowWithScrollBars:

class Window {
public:
//@ draw window and contents
virtual void display() const { std::cout << "display Window" << "\n"; }
};

class WindowWithScrollBars : public Window {
public:
virtual void display() const { std::cout << "display WindowWithScrollBars" << "\n"; }
};

//@ 产生截断,调用 Window::display
void printNameAndDisplay(Window w) {
w.display();
}

//@ 调用 Window::WindowWithScrollBars
void printNameAndDisplay(Window& w) {
w.display();
}

WindowWithScrollBars wwsb;
printNameAndDisplay(wwsb);

当调用 printNameAndDisplay 时参数类型从 WindowWithScrollBars 被隐式转换为 Window。 该转换过程通过调用 Window 的拷贝构造函数来进行。 导致的结果便是函数中的 w 事实上是一个 Window 对象, 并不会调用多态子类 WindowWithScrollBars 的 display()。

特殊情况

一般情况下相比于传递值,传递常量引用是更好的选择。但也有例外情况,比如内置类型和STL迭代器和函数对象。

  • 内置类型传值更好是因为它们小,而一个引用通常需要32位或者64位的空间。
  • 但对象小并不意味着拷贝构造的代价不高!比如STL容器通常很小,只包含一些动态内存的指针。然而它的拷贝构造函数中, 必然会分配并拷贝那些动态内存的部分。
  • STL 迭代器和函数对象也应当被传值,这是因为它们在 STL 中确实是被这样设计的,同时它们的拷贝构造函数代价并不高。

总结

  • 用传引用给 const 取代传值。通常情况下它更高效而且可以避免类截断问题。
  • 这条规则并不适用于内建类型及 STL 中的迭代器和函数对象类型。对于它们,传值通常更合适。
posted @ 2020-02-08 16:55  刘-皇叔  阅读(252)  评论(0编辑  收藏  举报