一道很坑的题_(:з」∠)_:
以下代码共调用多少次拷贝构造函数?
// 《深度探索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 匿名对象
为了让大家更好地理解上面那道题,我们先来介绍一下什么是匿名对象?
匿名对象可以理解为是一个临时对象,一般系统自动生成的,如你的函数返回一个对象,这个对象在返回时会生成一个临时对象。
语法:类名();
注意:匿名对象,生命周期只在它定义的那一行。
我借助以下代码调试向大家说明:
#include <iostream>
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;
}
我们发现44行这个匿名对象调了构造函数之后,马上又调了析构函数。
2.匿名对象的用处
我们平时调用类的成员函数,是不是先定义一个对象,通过对象去调用。
匿名对象还可以这样调用:
还有,如果你只是单纯的想要传参,也可以用匿名对象:
但是我们发现左边传参的方式调用了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 铺垫
如果是以下的代码,我们的程序会调用几次拷贝构造?
ok,我们先自己来算一下。
x传参拷贝构造1
次,33、34行各拷贝构造1
次,传值返回拷贝构造1
次,41返回的结果又拷贝构造给ret,又是1
次。是不是应该总共5
次呀!
ok,我们看运行结果:
怎么回事?
本来是不是应该这样啊,但是大家注意,41行是不是连续的拷贝构造啊,编译器会把它合二为一,相当于直接用w去拷贝构造ret了。
如果大家还是不能确信,请看下图:
这个返回来的w的临时对象就是赋值给ret,因为ret对象已经存在。阻断了编译器的优化。
3.2 解题
所以这道题总共一个调用了7次拷贝构造函数,我们运行一下看是不是:
哈哈,你答对了吗?(:з」∠)