本文是学习《Effective C++》的笔记,记录函数引用传值与值传值在效率上面的区别。
1.效率分析
考虑以下代码:
class Fruit
{
public:
virtual void ShowName()const;
private:
std::string strName;
};
class Apple:public Fruit
{
public:
virtual void ShowName() const;
private:
std::string strArea;
};
void PrintFruitName(Apple apple)
{
// do something
}
int main(int argc, char* argv[])
{
Apple apple;
PrintFruitName(apple);
return 0;
}
首先创建Apple对象,然后函数PrintFruitName(Apple apple)以值传递参数,将实参转化为形参,会以apple为蓝本调用Apple的拷贝构造函数,依次构造基类Fruit的std::string成员对象strName,基类Fruit对象,子类Apple的std::string成员对象strArea以及子类Apple自身。
函数PrintFruitName执行完成后,会按照构造顺序的相反顺序依次析构子类Apple,Apple成员是std::string对象,基类Fruit,Fruit的std::string对象,也就是执行此函数会造成四次构造与四次析构函数的调用。
当以引用传值时:
void PrintFruitName(const Apple& apple)
{
// do something
}
没有任何构造函数与析构函数调用,效率会高很多,这里的const也是十分必要的,原先以值传递Apple对象,PrintFruitName函数也只能对其副本形参进行修改,不会影响传入的参数。现在是const Apple& 引用传参,则用const关键字明确告诉调用者函数不会改变传入的Apple参数,并且提升效率,和按值传递达到一样的效果。
2.对象切割问题
首先分析以下这段代码的输出结果:
#include <iostream>
class Fruit
{
public:
virtual void ShowName()const;
private:
std::string strName;
};
void Fruit::ShowName()const
{
std::cout << "Fruit!!!" << std::endl;
}
class Apple:public Fruit
{
public:
virtual void ShowName() const;
};
void Apple::ShowName() const
{
std::cout << "Apple!!!" << std::endl;
}
void PrintFruitName(Fruit fruit)
{
fruit.ShowName();
}
int main(int argc, char* argv[])
{
Apple apple;
PrintFruitName(apple);
return 0;
}
基础不扎实的我认为由于多态,子类对象转化为基类对象,执行虚函数时会调用子类的函数实现,会输出 “Apple!!!”
VS2019的输出结果为:
What!!!怎么和想得不一样?
果然我的基础不够扎实,原来以值传递参数会产生对象的切割问题,当子类对象Apple,传递给PrintFruitName(Fruit fruit)时,要调用fruit的拷贝构造函数,即:Fruit(apple),以子类对象拷贝构造基类对象,造成子类Apple的特化性质被切割掉,仅仅留下基类Fruit对象。
这是由于函数形参真正是由Fruit的copy构造函数构造了它, 在调用opy构造函数Fruit(apple)时仅仅以apple的基类部分来构造Fruit。所以形参仅仅表现为基类的性质,不存在多态,所以输出"Furit!!!"。
改成以引用传值,则和预想一直,输出"Apple !!!"
void PrintFruitName(const Fruit& fruit)
{
fruit.ShowName();
}
3.内置数据类型
而对于内置数据类型,迭代器,函数对象都习惯被设计为用值传递。
4.结论
函数传参时尽量以引用传值代替值传值,以引用传值比值传值更加高效,并且可以避免切割问题。