为何禁用非强制拷贝消除后C++11与C++17的对象构造行为不同?
为何禁用非强制拷贝消除后C11与C17的对象构造行为不同?
嗨,这个问题其实戳中了C++17对对象初始化语义做的一个关键改变,很多人第一次碰到都会困惑,我来给你掰扯清楚:
首先得明确-fno-elide-constructors的真实作用:这个选项是用来禁用编译器可选的拷贝/移动消除(也就是我们常说的NRVO/RVO里那些编译器可做可不做的优化),但它管不了C17里新增的、语言规则强制要求的“无拷贝”语义——甚至说,有些场景在C17里根本就不存在“拷贝/移动”的步骤,自然也不需要“消除”。
先看C++11的老逻辑
在C++17之前,你写Thing t2 = f();时,整个流程是分层的:
- 进入
f(),在函数栈上构造局部对象t1; - 返回
t1时,因为你禁用了NRVO,编译器必须把t1移动到函数的返回值临时对象里(所以会触发移动构造); - 接着,用这个临时对象再移动构造
t2(又一次移动构造); - 最后依次销毁临时对象和
t1。
所以你会看到两次移动构造的输出,加上多次析构。
再看C++17的语义重构
C++17彻底改了prvalue(纯右值)的语义:prvalue不再是一个实际的临时对象,而是一个“对象初始化的蓝图”。当你调用f()返回Thing类型的prvalue,并用它初始化t2时,语言规则要求直接在t2的内存位置上完成整个初始化流程。
放到你的代码里,具体表现就是:
- 当
f()里构造t1时,编译器可以直接把t1的构造位置安排在t2的内存上(哪怕你禁用了NRVO); - 返回
t1时,不需要任何移动或拷贝——因为t1本质上就是t2在函数内部的“别名”; - 所以你只会看到一次构造(直接构造
t2),f()里的t1析构其实就是函数退出时清理这个“别名”的过程,而t2的析构则在main函数结束时执行。
这里的关键是:C++17的这个变化不是“优化”,而是语言语义的一部分——也就是说,哪怕你用-fno-elide-constructors禁用了可选优化,这种直接初始化目标对象的行为依然是必须遵守的规则,所以不会出现移动/拷贝构造的调用。
备注:内容来源于stack exchange,提问作者sam
相关产品推荐
相关产品推荐

