函数调用中拷贝构造函数为何延迟调用?
万能引用函数模板与普通引用函数的行为差异解析
结论
这些行为完全符合C++标准的预期,核心差异来自万能引用的类型推导与引用折叠规则。
Foo模板的行为拆解
当调用Foo(x)时,x是一个非const左值,根据C++的万能引用推导规则:
- 万能引用
T&&遇到左值时,T会被推导为对应的左值引用类型——这里T推导为X&(引用折叠规则:X& &&最终等价于X&) - 此时
Foo被实例化为以下代码:
X& Foo(X& t) { X& out{std::forward<X&>(t)}; // out是t的引用,也就是main函数中x的别名 std::cout << "out = " << out.s_ << std::endl; return out; }
这就是为什么输出out = name:out并不是新构造的X对象,它只是main里x的引用,访问out.s_自然就是x的s_值。
当return out时,返回的是X&类型,main中auto y = Foo(x)会将该引用指向的x拷贝构造为y,因此触发拷贝构造函数X(const X&),y的s_被设置为copy_con_val,对应输出中的X(const X&)和最终的all done: y = copy_con_val。
Bar普通函数的行为对比
Bar是普通函数,参数明确为const X&,函数内的X out{x};会直接调用拷贝构造函数创建一个新的X对象,out的s_被初始化为copy_con_val,因此输出out = copy_con_val。后续返回out时即便触发返回值优化(RVO),也只是直接在main的y内存上构造对象,不影响out本身是新对象的事实,所以输出符合预期。
如何让Foo实现预期行为
如果你希望Foo中构造新的X对象,需要将返回值和局部变量的类型改为移除引用后的T,可以用std::remove_reference_t实现:
#include <type_traits> // 需要包含头文件 template <typename T> std::remove_reference_t<T> Foo(T&& t) { std::remove_reference_t<T> out{std::forward<T>(t)}; std::cout << "out = " << out.s_ << std::endl; return out; }
此时调用Foo(x)会实例化为返回X类型,out是新构造的对象,会输出out = copy_con_val,同时返回时的RVO可以避免额外拷贝。
内容的提问来源于stack exchange,提问作者Stephen
相关产品推荐
相关产品推荐

