NRVO 什么时候开始?需要满足哪些要求?

问题描述

我有以下代码

org.springframework.http.converter.HttpMessageNotReadableException: required request body is missing: public TestController.processRequest(java.lang.String)
    at org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor.readWithMessageConverters(RequestResponseBodyMethodProcessor.java:161)
    at org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor.resolveArgument(RequestResponseBodyMethodProcessor.java:131)
    at org.springframework.web.method.support.HandlerMethodArgumentResolverComposite.resolveArgument(HandlerMethodArgumentResolverComposite.java:121)
    at org.springframework.web.method.support.invocableHandlerMethod.getmethodArgumentValues(invocableHandlerMethod.java:167)
    at org.springframework.web.method.support.invocableHandlerMethod.invokeForRequest(invocableHandlerMethod.java:134)
    at org.springframework.web.servlet.mvc.method.annotation.ServletinvocableHandlerMethod.invokeAndHandle(ServletinvocableHandlerMethod.java:105)
    at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:878)
    at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:792)
    at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)

以上代码产生错误#include <iostream> struct Box { Box() { std::cout << "constructed at " << this << '\n'; } Box(Box const&) { puts("copy"); } Box(Box &&) = delete; ~Box() { std::cout << "destructed at " << this << '\n'; } }; auto f() { Box v; return v; // is it eligible for NVRO? } int main() { auto v = f(); }

但是如果我更改代码以返回纯右值,则代码有效。

call to deleted constructor/function in both gcc and clang

为什么会这样?是因为删除移动构造函数吗? 如果我将复制和移动构造函数都更改为显式,它也会产生错误

如果标记删除,为什么不能简单地使用定义的复制构造函数

编辑:

auto f() {
      Box v;
      return Box(); // is it because of copy elision? 
  }
   

编辑 2:

      compiled with -std=c++20 in both gcc and clang,error.
      compiled with -std=c++17 gcc,compiles.
      compiled with -std=c++17 clang,error.

解决方法

这个程序有两个不同的潜在错误。

auto v = f(); 是 C++14 及以下的错误,因为该操作在逻辑上是移动构造,而不是 C++17 及更高版本的错误,因为它是临时而不是移动的具体化建造。这是 C++17 保证的复制省略特性,区别于 NRVO。

return v; 在所有 C++ 版本中都是错误的,因为它在逻辑上是一个移动构造,并且构造函数需要存在且可访问。 NRVO 大部分时间都会优化构造函数,但 NRVO 不是强制性的,它只是允许的,因此它不能使原本无效的程序有效。但是,gcc 不会在 std=c++17 及更低版本中捕获此错误。相反,它回退到复制构造函数。这似乎是一个 gcc 错误。

C++17 不强制要求 NRVO。当操作数是纯右值时,它要求在 return 语句中进行复制省略,因此在这种情况下不需要存在复制/移动构造函数。这就是 return Box(); 起作用的原因。

,

显然影响复制/移动省略 (Quoting the draft) 的 C++20 标准发生了变化:

受影响的子条款:[class.copy.elision]
更改:一个函数 返回一个隐式可移动实体可能会调用一个构造函数 对与返回表达式的类型不同的类型的右值引用。 可以使用 move 抛出函数和 catch 子句参数 构造函数。

以及给出的例子:

struct base {
  base();
  base(base const &);
private:
  base(base &&);
};

struct derived : base {};

base f(base b) {
  throw b;                      // error: base(base &&) is private
  derived d;
  return d;                     // error: base(base &&) is private
}

还有来自 [class.copy.elision] 的总结(强调我的):

隐式可移动实体是一个自动存储持续时间的变量,它可以是非易失性对象或右值引用 到非易失性对象类型。在下面的复制初始化中 上下文,在尝试移动操作之前首先考虑移动操作 复制操作:

如果表达式中的一个返回([stmt.return]) 或 co_return ([stmt.return.coroutine]) 语句是一个(可能 括号中的) id 表达式命名一个隐式可移动实体 在最里面的正文参数声明子句中声明 封闭函数或 lambda 表达式,或

如果操作数为 throw 表达式 ([expr.throw]) 是一个(可能有括号) id 表达式,用于命名属于的隐式可移动实体 不包含最里面的复合语句的范围 try-block 或 function-try-block(如果有)的复合语句或 ctor-initializer 包含 throw 表达式,重载决议为 选择复制或 return_value 重载的构造函数 调用首先执行,就好像表达式或操作数是一个 右值。如果第一次重载决议失败或没有执行, 再次执行重载决议,考虑表达式或 操作数作为左值

[注意 3:这个两阶段重载解析被执行不管 是否会发生复制省略。它确定构造函数或 如果未执行省略,则将调用 return_value 重载,和 所选的构造函数或返回值重载必须可访问 即使呼叫被忽略。 — 尾注]