为何删除拷贝/移动构造函数时C++命名返回值优化编译失败?
问题场景
在gcc 13.1环境下,测试C++11/17/20/23标准代码时发现:
- 当结构体
Foo删除拷贝或移动构造函数后,返回命名局部对象的makeFoo()无法编译;若不删除构造函数,命名返回值优化(NRVO)会生效,全程无拷贝/移动操作。 - 直接返回prvalue(临时对象)的
makeFoo()不受构造函数删除的影响,普通返回值优化(RVO)正常工作,代码可编译运行。
测试代码示例
#include <memory> #include <iostream> struct Foo{ Foo(int v): a{v} { std::cout << "Create!\n"; } ~Foo() { std::cout << "Destruct!\n"; } Foo(const Foo&)=delete; Foo(Foo&&)=delete; int a; }; // 无法编译! Foo makeFoo() { Foo foo{5}; return foo; } // 可正常编译! //Foo makeFoo() { // return Foo{5}; //} int main() { auto foo = makeFoo(); std::cout << "Hello world! " << foo.a << "\n"; }
现象解释
核心差异在于C++标准对两种返回值优化的「合法性前提」要求不同:
1. 命名返回值优化(NRVO):优化可选,需保证未优化路径合法
NRVO针对返回函数内命名局部对象的场景(比如代码中的foo)。虽然主流编译器(如gcc)几乎都会触发NRVO,直接在调用方的内存空间构造对象,跳过拷贝/移动,但C++标准明确规定:
NRVO是编译器的可选优化,无论是否执行优化,代码必须保证「如果不做优化时也能合法编译」。
当你写return foo;时,编译器首先要检查:如果不做NRVO,是否能通过拷贝或移动构造函数将foo转移到返回值位置?由于Foo删除了这两个构造函数,这条「未优化路径」是非法的,因此编译器直接报错,不会考虑是否执行优化。
反之,若不删除构造函数,编译器会触发NRVO,实际运行时不会调用拷贝/移动构造函数——但这只是编译器的优化行为,并非标准强制要求。
2. 普通返回值优化(RVO):C++17及以后为强制语义,无需依赖拷贝/移动构造函数
针对直接返回prvalue(比如return Foo{5};这种临时对象)的场景,C++17对语言语义做了重大修改:
此类场景下的RVO不再是可选优化,而是语言的强制语义——编译器必须直接在调用方的目标变量位置构造临时对象,完全跳过拷贝/移动步骤。
这意味着从C++17开始,return Foo{5};等价于直接在main()的foo变量位置构造对象,根本不会触发拷贝/移动构造函数的调用,自然也不需要这些构造函数存在。哪怕删除了拷贝/移动构造函数,代码依然合法。
在C11/14标准中,RVO仍是可选优化,但gcc等主流编译器会主动执行优化,因此实际测试中代码也能编译通过——不过严格来说,C11/14标准并未强制要求这种情况下可以跳过构造函数检查,属于编译器的宽松实现。
内容的提问来源于stack exchange,提问作者Nathan Doromal

