一道关于拷贝构造的习题讲解

本文详细解析了一道关于C++拷贝构造函数的习题,涉及匿名对象的使用和编译器的优化。通过多个代码示例解释了在不同场景下拷贝构造的调用次数,强调了在连续步骤的构造+拷贝构造或拷贝构造+拷贝构造时,编译器可能进行优化,将两次操作合并为一次。
摘要由CSDN通过智能技术生成


题目描述

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

Widget f(Widget u)
{
    Widget v(u);
    Widget w = v;
    return w;
}

int main()
{
    Widget x;
    Widget y = f(f(x));
}

在做这道题之前,要先铺垫一些前置知识!

前导一

先看以下代码,调用了多少次拷贝构造呢?

class Weight
{
public:
	//构造函数
	Weight()
	{
		cout << "Weight()" << endl;
	}
	
	//拷贝构造
	Weight(const Weight& w)
	{
		cout << "Weight(const Weight& w)" << endl;
	}
};

void f(Weight u) //传值传参
{
	;
}

int main()
{
	Weight w;
	f(w);

	return 0;
}

因为 f 函数的形参部分是传值传参,所以会调用一次拷贝构造

在这里插入图片描述

前导二

以下代码,调用了多少次拷贝构造呢?

class Weight
{
public:
	//构造函数
	Weight()
	{
		cout << "Weight()" << endl;
	}
	
	//拷贝构造
	Weight(const Weight& w)
	{
		cout << "Weight(const Weight& w)" << endl;
	}
};

Weight f(Weight u) // 传值传参
{
	Weight v(u); // 用u拷贝构造了v
	Weight w = v; // 用v拷贝构造了w

	return w;
}

int main()
{
	Weight x;
	f(x);

	return 0;
}

首先传值传参会调用一次拷贝构造;

接着用 u 拷贝构造了 v,那么又会调用一次拷贝构造;

然后用 v 拷贝构造了 w,又会调用一次拷贝构造;

最后返回了 w,那么又会调用一次拷贝构造;

所以总共是 4 次拷贝构造:

在这里插入图片描述

前导三

我们对前导二的代码修改一下

class Weight
{
public:
	//构造函数
	Weight()
	{
		cout << "Weight()" << endl;
	}
	
	//拷贝构造
	Weight(const Weight& w)
	{
		cout << "Weight(const Weight& w)" << endl;
	}
};

Weight f(Weight& u) // 引用传参
{
	Weight v(u); // 用u拷贝构造了v
	Weight w = v; // 用v拷贝构造了w

	return w;
}

int main()
{
	Weight x;
	f(x);

	return 0;
}

f 函数形参部分用了 引用传参,那么就只会调用 3 次拷贝构造

在这里插入图片描述

前导四

我们对前导三的代码修改一下

class Weight
{
public:
	//构造函数
	Weight()
	{
		cout << "Weight()" << endl;
	}
	
	//拷贝构造
	Weight(const Weight& w)
	{
		cout << "Weight(const Weight& w)" << endl;
	}
};

Weight& f(Weight& u) // 引用传参
{
	Weight v(u); // 用u拷贝构造了v
	Weight w = v; // 用v拷贝构造了w

	return w; // 拿引用作为返回值
}

int main()
{
	Weight x;
	f(x);

	return 0;
}

我们把 f 函数的返回值改成了 引用作返回值,那么就只会调用 2 次拷贝构造

在这里插入图片描述

匿名对象

这里我们要提一个新的概念:匿名对象

比如下面这段代码:

class Weight
{
public:
	//构造函数
	Weight()
	{
		cout << "Weight()" << endl;
	}
	
	//拷贝构造
	Weight(const Weight& w)
	{
		cout << "Weight(const Weight& w)" << endl;
	}

	//析构函数
	~Weight()
	{
		cout << "~Weight()" << endl;
	}
};

int main()
{
	// 匿名对象,声明周期只在这一行
	Weight();

	return 0;
}

Weight() 是一个匿名对象,它会默认去调用 拷贝构造+析构函数,并且它的声明周期只在这一行里面,到了下一行时,就不存在了,因为已经被析构了。

在这里插入图片描述

前导一

既然知道了匿名对象,我们来看下面这段代码。调用了多少次拷贝构造呢?

class Weight
{
public:
	//构造函数
	Weight()
	{
		cout << "Weight()" << endl;
	}
	
	//拷贝构造
	Weight(const Weight& w)
	{
		cout << "Weight(const Weight& w)" << endl;
	}

	//析构函数
	~Weight()
	{
		cout << "~Weight()" << endl;
	}
};

Weight f(Weight u) 
{
	Weight v(u); 
	Weight w = v; 

	return w; 
}

int main()
{
	
	f(Weight());

	return 0;
}

运行结果
在这里插入图片描述

为什么是 3 次呢?

因为这里涉及到编译器的优化,它把 Weight() 的拷贝构造 和 f 函数形参部分的构造 合二为一 了,所以只有 3 次拷贝构造,但是却有 4 次析构。

这里理解起来可能有点抽象,我再举个例子吧

在这里插入图片描述

前导二

我们再来看以下代码

class Weight
{
public:
	//构造函数
	Weight()
	{
		cout << "Weight()" << endl;
	}
	
	//拷贝构造
	Weight(const Weight& w)
	{
		cout << "Weight(const Weight& w)" << endl;
	}

	//析构函数
	~Weight()
	{
		cout << "~Weight()" << endl;
	}
};

Weight f(Weight u) 
{
	Weight v(u); 
	Weight w = v; 

	return w; 
}

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

运行结果:

在这里插入图片描述

为什么会是 4 次呢?

因为在一个表达式中,如果出现连续步骤的构造+拷贝构造,或者拷贝构造+拷贝构造,那么胆大的编译器可能会进行优化,合二为一。

也就是说,下面的第 4 次构造和第 5 次构造合为一次了:

在这里插入图片描述

前导三

上面说了,如果出现连续步骤的 构造+拷贝构造,或者 拷贝构造+拷贝构造,那么胆大的编译器可能会进行优化,合二为一。

那么如何来证实这句话是不是真的呢?如果不出现不连续的情况,那么编译器是不是就不会进行 合二为一 呢?

我们可以来举例验证一下

class Weight
{
public:
	//构造函数
	Weight()
	{
		cout << "Weight()" << endl;
	}
	
	//拷贝构造
	Weight(const Weight& w)
	{
		cout << "Weight(const Weight& w)" << endl;
	}

	//赋值重载
	Weight& operator=(const Weight& w)
	{
		cout << "Weight& operator=(const Weight& w)" << endl;
		return *this;
	}

	//析构函数
	~Weight()
	{
		cout << "~Weight()" << endl;
	}
};

Weight f(Weight u) 
{
	Weight v(u); 

	Weight w = v; 

	return w; 
}

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

运行结果:

在这里插入图片描述

可以看到,在这种情况下,编译器优化的步骤就会被阻断。

这个实验只想说明,对于编译器合二为一的优化,只有在连续步骤的 构造+拷贝构造,或者 拷贝构造+拷贝构造 的情况下,才会进行。

所以 Weight ret = f(x);这种写法是最优的,因为只会调用 4 次拷贝构造;

Weight ret; ret = f(x); 这种写法会调用 4 次拷贝构造 + 1 次赋值。

回到开头

那么我们的前导知识已经补充完了,再来回到文章开头,以下代码共调用多少次拷贝构造函数?

Widget f(Widget u)
{
    Widget v(u);
    Widget w = v;
    return w;
}

int main()
{
    Widget x;
    Widget y = f(f(x));
}

分析过程如下:

在这里插入图片描述

f(x):用 w 拷贝构造了临时对象,然后用这个临时对象作为表达式的返回值又去拷贝构造,那么这个时候,连续的步骤会被优化成 1 次。

也就是说 4 和 5 会被优化成一次;8 和 9 会被优化成一次;

所以程序最终调用了 7 次拷贝构造。

总结

总结一下,如果程序中出现连续步骤的 构造+拷贝构造,或者 拷贝构造+拷贝构造,那么编译器可能会进行优化,合二为一。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Albert Edison

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值