默认情况下C++以值方式传递对象至函数。
除非你另外指定,否则函数参数都是以实际参数的复件位初始值。而调用端所获得也是函数返回值的一个复件。这些复件由对象的复制构造函数产出。
这可能使得pass-by-value成为昂贵的操作。
比如:
class Person
{
private:
string lname;
string fname;
public:
Person();
virtual ~Person();
virtual void show()const;
};
class Student : public Person
{
public:
Student();
~Student();
...
private:
string schoolName;
string schoolAddress;
virtual void show()const;
};
考虑以下代码:
bool validateStudent(Student s);
Student p;
bool pisOK =validateStudent(s);
当上面的函数被调用会发生什么事情?
首先Student
的复制构造函数会被调用,以p为蓝本将s初始化。
其次validateStudent
返回s会被销毁。
因此,对这个函数而言。参数传递的成本是:一次Student 复制构造函数调用,一次Student析构函数调用.
Student
对象内有两个string
对象,每次构造一个Student
对象也就构造了两个string
对象。
此外Student
对象继承自Person
对象,一个Person
对象又有两个string
对象.
最终结果是以值方式传递一个Student
对象会导致调用一次Student
复制构造函数、一次Person
复制构造函数、四次复制构造函数。
当Student
复件被销毁,还要调用析构函数,总体成本是:六次构造函数和六次析构函数
如果我们以pass by reference to-const来传递,效率就高很多了:
bool validateStudent(const Student& s);
上面这种方式,没有任何构造函数或析构函数被调用。
此外以by reference方式传递参数也可以避免对象切割问题.
当一个派生类对象以by value方式传递并被视为一个基类对象,基类的构造函数会被调用,而派生的特化性质全被切掉了,仅仅留下一个基类对象.
假如有这样一个函数,用来打印上面的类的信息:
void ShowInformation(Person P)
{
p.show();
}
当你以以下形式调用这个函数,会发生什么情况?
Student s;
ShowInformation(s);
s会被构造成为一个Person对象,它是按值传递,派生的所有特化信息都会被切除,在这个函数内调用的总是Person::show
而不是Student::Show
.
解决切割问题的办法,就是by reference-to-const的方式传递:
void ShowInformation(const Person& P)
{
p.show();
}
现在,传进来的是什么类型,p就表现出什么类型。
在C++中,references往往以指针实现出来,因此pass by reference通常意味真正传递是指针。如果是内置类型的话(比如int),pass by value往往效率更高。这个也适用于STL的迭代器和函数对象,因为习惯上它们都被设计为passed by value.
注意:
不要认为小型类型都是适用于pass-by-value, 甚至它们是用户自定义的类也可以,对象小并不意味着复制构造函数不昂贵,某些编译器对待内置类型和用户自定义类型的态度截然不同,比如某些编译器拒绝把只有一个double组成的对象放进缓存中,却乐意对一个double这样做, 两个拥有相同的底层表述,当这种事情发生时,你应该以by reference
方式传递此对象,因为编译器会将指针放进缓存内.
此外,另一个理由是,用户自定义类型,其大小很容易变化,一个类可能现在很小,将来有可能变得很大。
总结:
1.按引用传递通常比较高效,此外还可以避免切割问题.
2.以上规则不适用于内置类型,STL迭代器和函数对象。