You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C++17下有无自定义拷贝构造函数时NRVO失效原因咨询

场景复现

我编写了如下演示代码:

#include <iostream>
using namespace std;
class Test {
public:
    Test();
    ~Test();
    Test(const Test&);
};
Test::Test(const Test& rhs) {
    cout << "do copy constructor function" << endl;
}
Test::Test()
{
    cout << "do constructor function" << endl;
}
Test::~Test() {
    cout << "do deconstructor function" << endl;
}
Test fun1() {
    Test tmp;
    return tmp;    // NRVO
}
int main() {
    Test t1 = fun1();
    return 0;
}

在VS2017 Release模式下运行上述代码,C标准设置为C17,运行结果符合预期:拷贝构造函数未被调用,NRVO正常生效。

但当注释掉自定义的拷贝构造函数后再次运行,代码如下:

#include <iostream>
using namespace std;
class Test {
public:
    Test();
    ~Test();
};
Test::Test()
{
    cout << "do constructor function" << endl;
}
Test::~Test() {
    cout << "do deconstructor function" << endl;
}
Test fun1() {
    Test tmp;
    return tmp;    // NRVO
}
int main() {
    Test t1 = fun1();
    return 0;
}

运行结果显示NRVO似乎失效:构造函数仅执行1次,但析构函数执行了2次。

原因解析

这个现象不是NRVO失效,是VS2017对可平凡复制类型的返回值做的特殊ABI优化导致的,具体逻辑如下:

  • 当手动定义拷贝构造函数时,Test属于非可平凡复制类型,VS2017会正常应用NRVO优化:直接在main函数栈帧为t1预留的内存地址上构造对象,整个过程不会产生fun1内的局部临时对象,也不会调用拷贝构造,最终构造、析构各执行1次,和观测结果一致。
  • 当注释掉自定义拷贝构造函数后,编译器会自动生成隐式的可平凡复制拷贝构造函数。对于这类不需要执行任何额外逻辑的平凡类型,VS2017会选择效率更高的返回路径:不会做NRVO的地址透传,而是先在fun1的栈帧内构造局部对象tmp,函数返回时直接通过CPU内存拷贝指令把tmp的内存数据逐字节复制到main栈帧中t1所在的内存位置。
    这个逐字节拷贝的过程不会执行任何构造函数逻辑,所以全程只会观测到1次构造函数调用,也就是fun1内tmp的构造。
  • 析构执行2次的原因非常直接:这个流程中实际存在两个独立生命周期的Test对象:一个是fun1栈帧内的tmp,在fun1返回前会执行一次析构;另一个是main栈帧内通过内存拷贝得到的t1,在main函数结束前会执行第二次析构。两个对象都会调用你自定义的析构函数,所以最终会输出2次析构日志。

补充说明:这个实现是VS2017(v141工具集)的特定行为,不是C++标准的强制要求。对可平凡复制类型来说,直接内存拷贝的开销比NRVO的地址传递逻辑更低,编译器是出于性能考量选择了这个返回路径,不属于优化bug。

内容的提问来源于stack exchange,提问作者f1msch

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.02 00:01:24