大学IT网 - 最懂大学生的IT学习网站! QQ资料交流群:367606806
当前位置:大学IT网 > C++技巧 > C++用传引用给const取代传值

C++用传引用给const取代传值

关键词:C++const  阅读(462) 赞(15)

[摘要]本文主要是对C++用传引用给const取代传值的讲解,希望对您学习C++有所帮助!

缺省情况下,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 对象的显示方法。

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

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 将表现得像理论传入的那种窗口。

假定你掀开编译器的盖头偷看一下,你会发现用指针完成引用是非常典型的做法,所以以引用传递某物理论上通常意味着传递一个指针。由此可以得出结论,假定你有一个内建类型的对象(例如,一个 int),以传值方式传递它常常比传引用方式更高效。那么,关于内建类型,当你需求在传值和传引用给 const 之间做一个选择时,没有道理不选择传值。异常的建议也适用于 STL 中的迭代器(iterators)和函数对象(function objects),由于,作为惯例,它们就是为传值设计的。迭代器(iterators)和函数对象(function objects)的完成有责任保证拷贝的高效并且不受切断效果的影响。(这是一个“规则如何变化,依赖于你运用 C++ 的哪一个部分”的实例。)

内建类型很小,所以有人就断定一切的小类型都是传值的上等候选者,即使它们是用户定义的。这样的推论是不可靠的。仅仅由于一个对象小,并不意味着调用它的拷贝构造函数就是廉价的。很多对象——大多数 STL 容器也在其中——容纳的和指针一样,但是拷贝这样的对象必需同时拷贝它们指向的每一样东西。那可以是非常昂贵的。

即使当一个小对象有一个廉价的拷贝构造函数,也会存在功用效果。一些编译器对内建类型和用户定义类型并不一视同仁,即使他们有异常的底层表示。例如,一些编译器拒绝将仅由一个 double 组成的对象放入一个寄存器中,即使在常规上它们非常情愿将一个地道的 double 放入那里。假定发作了这种事情,你以传引用方式传递这样的对象更好一些,由于编译器理所当然会将一个指针(引用的完成)放入寄存器。

小的用户定义类型不一定是传值的上等候选者的另一个缘由是:作为用户定义类型,它的大小常常变化。一个如今较小的类型在将来版本中可以变得更大,由于它的内部完成可以会变化。甚至当你换了一个不同的 C++ 完成时,事情都可以会变化。例如,就在我这样写的时分,一些标准库的 string 类型的完成的大小就是另外一些完成的七倍。

通常情况下,你能合理地假定传值廉价的类型仅有内建类型及 STL 中的迭代器和函数对象类型。对其他任何类型,请遵照本 Item 的建议,并用传引用给 const 取代传值。

Things to Remember

·用传引用给 const 取代传值。典型情况下它更高效而且可以避免切断效果。

·这条规则并不适用于内建类型及 STL 中的迭代器和函数对象类型。关于它们,传值通常更适合。



相关评论