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

使用纯右值函数返回值初始化对象是否会调用移动构造函数?

C++ 返回值复制消除规则验证疑问

我最初编写如下代码用于验证NRVO的效果:

struct test_nrvo {
    bool b;
    long x[100];  // Too large to return in register
};

test_nrvo tester(test_nrvo* p) {
    test_nrvo result{ p == &result };
    return result;
}

bool does_nrvo() {
    test_nrvo result(tester(&result));
    return result.b;
}

int main() {
  __builtin_printf("nrvo: %d\n", does_nrvo());
}

在我使用的编译器-O0优化等级下,该代码正常输出nrvo: 1。

之后我尝试编写类似代码,验证返回纯右值场景下的RVO行为:

struct test_rvo {
    bool b;
    test_rvo(test_rvo* p) : b(p == this) {}
};

test_rvo tester(test_rvo* p) {
    return test_rvo(p);
}

bool does_rvo() {
    test_rvo result(tester(&result));
    return result.b;
}

int main() {
  __builtin_printf("rvo: %d\n", does_rvo());
}

(上述两个代码片段附加日志的版本)

运行后该代码输出rvo: 0,这意味着参数p和构造函数内的this指针指向不同地址,说明过程中至少存在两个不同对象,发生了移动构造操作。

当我删除移动构造函数或将移动构造函数定义为非平凡时,代码输出rvo: 1,看起来该场景下允许发生复制消除。

但令人困惑的是,即便是删除了移动构造函数(该场景下绝不可能存在两个不同对象),GCC下的constexpr版本依然编译失败,这似乎是GCC的bug;Clang在constexpr场景下的行为也和非constexpr场景存在差异。我据此推测该场景属于允许但非强制的复制消除,请问该结论是否正确?如果正确,为什么函数调用返回的纯右值,和其他必须进行移动消除的纯右值存在规则差异?


回答

核心结论

你的推测完全正确,这个场景下的复制消除属于标准允许但不强制的优化,不属于C++17引入的强制复制消除范畴。

规则差异的根本原因

C++17新增的强制复制消除,适用边界非常明确:仅当纯右值直接用于初始化同类型的目标对象时,才会强制省略临时对象的复制/移动。这个规则本质是重新定义了纯右值的物化时机——纯右值只是对象的初始化规则描述,只有在真正需要分配内存的场景才会物化成为实际对象,直接在目标内存上构造,不需要经过临时对象,这个行为和类的复制/移动构造是否存在、是否可访问完全无关。

而你测试的函数返回纯右值场景,从C++第一版标准开始就属于可选的返回值优化(RVO)范围,从未被纳入强制优化,核心原因有两个:

  • 标准不约束ABI实现:强制要求跨函数的RVO,意味着所有编译器必须统一约定:调用方给被调用函数传入目标对象的地址,被调用函数直接在该地址上构造返回值。但大量现有平台的ABI已经沿用了旧的返回值传递规则,强制修改会彻底打破二进制兼容性,标准不可能做这种强约束。
  • 函数返回场景无法保证优化必然成立:很多函数存在多分支返回逻辑,不同分支可能返回不同的具名对象、不同的临时值,编译器不可能在所有代码路径下都保证能消除复制,因此只能把优化的决定权交给编译器实现,由编译器根据成本判断要不要做。

观测到的编译器行为解释

  • -O0下默认不做RVO输出rvo: 0,是因为无优化等级下编译器会尽量减少不必要的逻辑变换,加快编译速度。当类有可用的平凡移动构造时,移动临时对象的成本只有一次指针/值拷贝,开销极低,编译器干脆不做RVO;当你删除移动构造、或者移动构造是非平凡的(有额外逻辑、拷贝成本高),哪怕在-O0下编译器也会主动做RVO,避免调用高成本的构造函数。
  • 测到的NRVO在-O0下生效,只是GCC/Clang对具名返回值的优化逻辑和临时返回值不同,同样属于可选优化,不是标准强制要求。
  • constexpr场景下GCC编译失败、Clang行为不一致,确实是编译器实现bug:constexpr求值要求编译器模拟所有可观察的副作用,但可选复制消除的实现逻辑没有在constexpr求值器中被正确处理——实际上constexpr求值时编译器完全可以直接在目标对象上构造返回值,不需要走临时对象移动的路径,GCC目前在这块的实现存在已知缺陷。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.28 00:06:34