关于编译器对连续的构造+拷贝构造/连续的拷贝构造的优化

一道很坑的题_(:з」∠)_:

以下代码共调用多少次拷贝构造函数?

// 《深度探索C++对象模型》
Weight f(Weight u)
{
	Weight v(u);
	Weight w = v;

	return w;
}

int main()
{
	Weight x;
	Weight ret = f(f(x));
	return 0;
}

文章目录

  • 1 匿名对象
  • 2.匿名对象的用处
  • 3.题解
    • 3.1 铺垫
    • 3.2 解题

1 匿名对象

为了让大家更好地理解上面那道题,我们先来介绍一下什么是匿名对象?
匿名对象可以理解为是一个临时对象,一般系统自动生成的,如你的函数返回一个对象,这个对象在返回时会生成一个临时对象。
语法:类名();
注意:匿名对象,生命周期只在它定义的那一行

我借助以下代码调试向大家说明:

#include 
using std::cout;
using std::cin;
using std::endl;

class Weight
{
public:
	Weight()
	{
		cout << "Weight()" << endl;
	}

	Weight(const Weight& w)
	{
		cout << "Weight(const Weight& w)" << endl;
	}
	
	~Weight()
	{
		cout << "~Weight()" << endl;
	}
	
	Weight& operator=(const Weight& w)
	{
		cout << "Weight& operator=(const Weight& w)" << endl;
		return *this;
	}
	
	void print()
	{
		cout << "2022-5-25" << endl;
	}
};

// 《深度探索C++对象模型》
Weight f(Weight u)
{
	Weight v(u);
	Weight w = v;

	return w;
}

int main()
{
	Weight x;
	Weight();
	f(x);
	return 0;
}

关于编译器对连续的构造+拷贝构造/连续的拷贝构造的优化_第1张图片
我们发现44行这个匿名对象调了构造函数之后,马上又调了析构函数

2.匿名对象的用处

我们平时调用类的成员函数,是不是先定义一个对象,通过对象去调用。
匿名对象还可以这样调用:
关于编译器对连续的构造+拷贝构造/连续的拷贝构造的优化_第2张图片
还有,如果你只是单纯的想要传参,也可以用匿名对象:
关于编译器对连续的构造+拷贝构造/连续的拷贝构造的优化_第3张图片
但是我们发现左边传参的方式调用了4次拷贝构造,而右边传参的方式调用了3次拷贝构造。
为什么?因为编译器优化了,编译器会在一个步骤里面,比如说一个表达式里面,如果出现连续的构造+拷贝构造或者连续的拷贝构造,编译器会把它们优化。
左边没的说,因为是分开的,先构造了一个对象,然后把这个对象传参又调用了拷贝构造。
右边是构造了匿名对象之后立马作为参数传参,这个时候编译器就会把它们合二为一,直接构造传参的对象。

还记得我们以前说的拷贝构造的两种方式吗?

class A 
{
public:
	A(int x)
	{}
};

int main()
{
	A a1(1);
	A a2 = 2; // A(2)  -> A a2(A(2))   优化:直接构造
	return 0;
}

关于 A a2 = 2;这行代码,它会先拿2构造一个匿名对象(也叫临时对象),然后才会拿这个对象去拷贝构造一个a2。这个时候胆大的编译器也会优化,直接构造a2.

注意:C++标准并没有规定是否优化,完全取决于编译器,不过新一点的编译器一般都会做这个优化。

3.题解

3.1 铺垫

如果是以下的代码,我们的程序会调用几次拷贝构造?
关于编译器对连续的构造+拷贝构造/连续的拷贝构造的优化_第4张图片

ok,我们先自己来算一下。
x传参拷贝构造1次,33、34行各拷贝构造1次,传值返回拷贝构造1次,41返回的结果又拷贝构造给ret,又是1次。是不是应该总共5次呀!

ok,我们看运行结果:
关于编译器对连续的构造+拷贝构造/连续的拷贝构造的优化_第5张图片
怎么回事?
关于编译器对连续的构造+拷贝构造/连续的拷贝构造的优化_第6张图片
本来是不是应该这样啊,但是大家注意,41行是不是连续的构造+拷贝构造啊,编译器会把它合二为一,相当于直接用w去构造ret了。

如果大家还是不能确信,请看下图:
关于编译器对连续的构造+拷贝构造/连续的拷贝构造的优化_第7张图片
这个返回来的w的临时对象就是赋值给ret,因为ret对象已经存在。阻断了编译器的优化。

3.2 解题

关于编译器对连续的构造+拷贝构造/连续的拷贝构造的优化_第8张图片
所以这道题总共一个调用了7次拷贝构造函数,我们运行一下看是不是:
关于编译器对连续的构造+拷贝构造/连续的拷贝构造的优化_第9张图片
哈哈,你答对了吗?(:з」∠)
关于编译器对连续的构造+拷贝构造/连续的拷贝构造的优化_第10张图片

你可能感兴趣的