在我们写一个函数接口的时候,我们使用什么方法来进行参数传递呢?是“传值”还是“传引用”?两者会对我们程序的执行呈现出截然不同的结果。
void func(const ExampleClass& exp); //传引用
void func(ExampleClass exp); //传值
从执行效率来讲,传引用要优于传值,原因在于,在传值的时候,程序会调用传参类型的构造函数,而传引用却不会。除此之外,其对于程序执行的结果影响也是很明显的,看看以下代码:
//基类
class CBase
{
......
virtual void func1();
};
void CBase::func1()
{
std::cout << "CBase::func1" << std::endl;
}
//派生类
class CSon1 : public CBase
{
......
virtual void func1();
};
void CSon1::func1()
{
cout << "CSon1::func1" << endl;
}
void hostFunc(CBase exp)
{
exp.func1();
}
int main()
{
CSon1 son1;
hostFunc(son1);
return 0;
}
执行这一段程序,会输出:
CBase::func1
这个输出结果不是我们所需要的,我们希望看到CSon1::func1被执行,但其实没有。另外,从上述的代码中,我们可以看出,CSon1的拷贝构造函数会被调用,对于hostFunc函数而言,此次参数传递的成本是:一次CSon1构造函数的调用,一次CSon1析构函数的调用。看起来这样的开销不算大,但是当CSon1中的成员变量比较多的时候,里面所有的成员变量都会被构造一次,其开销就会明显变多。
有什么方法可以避免这么多的构造函数被调用呢?......”传引用“可以解决这个问题,对于上面的hostFunc函数,可以这样写:
void hostFunc(const CBase& exp)
{
exp.func1();
}
这种传递方式效率要高很多,没有任何构造函数和析构函数被调用。而且,也避免了CSon1功能被切割的问题(当一个派生类对象被以传值的方式传递并被视为其基类对象,基类的拷贝构造函数会被调用,而此时派生类的一些特有功能会被切割掉),此时,程序的执行结果为:
CSon1::func1
这正是我们希望看到的,程序执行了派生类的特有功能(CSon1::func1())。
有的程序员可能会想到通过指针传参也可以达到同样的目的,这种推测是正确的。比如这样:
void hostFunc(const CBase* exp)
{
exp->func1();
}
其执行的结果同上。如果我们查看C++编译器的底层实现代码,会发现,引用本质上还是通过指针来实现的,使用传引用与传指针实际上是一样的。
综上所述,在程序中,我们尽量使用传引用来代替传值,前者效率高且可以有效避免切个问题。