缺省情况下C++以by value方式传递对象至函数。除非另外指定了,否则函数参数都是以实际实参的副本为初值,而调用端所获得的亦是函数返回值的一个副本。这些副本是由对象的copy构造函数产出,这导致pass-by-value成为费时的操作。考虑以下class继承体系:
class Person{
public:
Person();
virtual ~Person();
....
private:
std::string name;
std::string address;
};
class Student:public Person{
public:
Student();
~Student();
...
private:
std::string schoolName;
std::string schoolAddress;
};
现在考虑以下代码,其中调用函数validateStudent,后者需要一个Student实参(by value)并返回它是否有效:
bool validateStudent(Student s);//函数以by value方式接受学生
Student plato;
bool platoIsOk=validateStudent(plato);//调用函数
当上述程序代码运行时,发生什么事情呢?
首先,Student的copy构造函数会被调用,以plato为蓝本将s初始化。同样明显地,当validateStudent返回,s会被销毁。因此,对此函数而言,参数的传递成本是“一次Student copy构造函数调用,加上一次Studnet析构函数调用”。
再细一点分析,Student对象内有两个string对象,所以每次构造一个Student对象也就构造了两个string对象。此外Studnet对象继承自Person对象,所以每次构造Student对象也必须构造出一个Person对象。一个Person7对象又有两个string对象在其中,因此每一次Person构造动作又需要承担两个sting构造动作。最终结果即以by value方式传递一个Student对象会导致调用一次Student copy构造函数,一次Person copy构造函数,四次string copy构造函数。当函数内的那个Student复件被销毁,每一个构造函数调用动作都需要一个对应的析构函数调用动作。因此,以by value方式传递一个Student对象,总体成本是“六次构造函数和六次析构函数”。
以上操作是很正常的操作行为,毕竟希望所有对象都能够被构造和析构。尽管如此,如果有办法避免那些构造和析构操作就太好了,以下就是本节的重点,即pass by reference-to-const。
示例:
bool validataStudent(const Student& s);
这种传递方式的效率高的多,主要是因为:没有任何构造函数和析构函数被调用,因此没有任何新对象被创建。修订后的这个参数声明中的const是重要的。原先的validateStudent以by value方式接受一个Student参数,因此调用者知道它们受到保护,函数内绝对不会对传入的Student作任何改变;validateStudent只能够对其副本做修改。但现在不一样,现在Student以by reference方式传递,将它声明为const是必要的。因为这样的话调用者就不能够更改Student里面的变量值。
另外,以by reference方式传递参数可以避免slicing(对象)切割问题。当一个derived class对象并且以by value方式传递,同时derived class被视为一个base class对象,base class的copy构造函数会被调用,而“造成此对象的行为像个derived class对象”的那些特化性质全被切割掉了,仅仅留下一个base class对象。这是一种很正常的情况,因为正是base class构造函数建立了它。但这种情况绝不是我们想要的。假设你在一组classes上工作,用来实现一个图形窗口系统:
class Window{
public:
...
std::string name() const;//返回窗口名称
virtual void display() const;//显示窗口和其内容
};
class WindowWithScrollBars:public Window{
public:
...
virtual void display() const;
};
在u这里,所有的Window对象都带有一个名称,我们可以通过name函数取得它。所有窗口都可以显示,可以通过display函数完成它。display是个virtual函数,这表明base class Window对象的显示方式和WindowWithScrollBars对象的显示方式不同。
假设你希望写个函数打印窗口名称,然后显示该窗口,下面是一个错误的例子:
void printNameAndDisplay(Window w)//不正确,参数可能被切割
{
std::cout<
当调用上述函数并将给传递一个WindowWithScrollBars对象,会发生什么事情呢?
WindowWithScrollBars wwsb;
printNameAndDisplay(wwsb);
在这里,参数w会被构造成一个Window对象,它是passed by value。而造成wwsb之所以是个WindowWithScrollBars对象的所有特化信息都会被切除。 在printNameAndDisplay函数内无论传递过来的对象原本是什么类型,参数w均像一个Window对象。因此在printNameAndDisplay内调用display,调用的总是Window::display,绝不会是WindowWithcrollBars::display.
解决切割问题的方法就是by reference-to-const的方式传递w:
void printNameAndDisplay(const Window& w)//很好,参数不会被切割
{
std::cout<
现在,传进来的窗口是什么类型,w就表现什么类型。
深究c++底层编译器,就会发现reference往往以指针实现出来,因此pass by reference通常意味真正传递的是指针。因此如果有个对象属于内置类型(例如int),pass by value往往比pass by reference的效率高些。通常情况下,pass-bu-value并不费时的唯一对象就是内置类型和STL的迭代器和函数对象。至于其它任何东西都请遵守本条款。