【问题标题】:C++ nrvo/copy elision with return statement in parenthesesC ++ nrvo/复制省略,括号中带有return语句
【发布时间】:2018-02-12 15:06:26
【问题描述】:

我正在玩弄以下代码,并使用我的 Visual Studio 2017 应用程序和两个不同的在线编译器得到了不同的结果。在发布模式下,Visual Studio 确实在这两种情况下都省略了复制/移动,而两个在线编译器只是在没有括号的 return 语句的情况下这样做。我的问题是:谁是对的,更重要的是,基本规则是什么。 (我知道您可以将括号与decltype(auto)syntax 结合使用。但这不是当前的用例)。

示例代码:

#include <iostream>
#include <cstdio>

struct Foo
{
    Foo() { std::cout << "default constructor" << std::endl; }
    Foo(const Foo& rhs) { std::cout << "copy constructor" << std::endl; }
    Foo(Foo&& rhs) { std::cout << "move constructor" << std::endl; }
    Foo& operator=(const Foo& rhs) { std::cout << "copy assignment" << std::endl; return *this; }
    Foo& operator=(Foo&& rhs) { std::cout << "move assignment" << std::endl; return *this; }
};

Foo foo_normal()
{
    Foo a{};
    return a;
}

Foo foo_parentheses()
{
    Foo a{};
    return (a);
}

int main()
{
    auto a = foo_normal();
    auto b = foo_parentheses();
    std::getchar();
}

在线编译器1: http://cpp.sh/75bux

在线编译器2: http://coliru.stacked-crooked.com/a/c266852b9e1712f3

Visual Studio 在发布模式下的输出是:

default constructor
default constructor

在其他两个编译器中,输出为:

default constructor
default constructor
move constructor

【问题讨论】:

  • 这让我很吃惊。我认为 C++17 需要复制省略,并且不允许兼容的实现打印 move constructor。
  • 看起来 gcc 将 (a) 理解为不是 object 的名称。也许将其标记为language-lawyer。
  • @Someprogrammerdude 有区别,但我想这在通常的日常编程中并不重要。例如 stackoverflow.com/a/25615981/3783662 。用例带有decltype(auto)
  • @nwp 是的,我也很惊讶,但括号表达式可能有一个特殊的(转换)规则。我现在认识到第一个编译器只有 c++14。

标签: c++ language-lawyer copy-elision nrvo


【解决方案1】:

这是the relevant quote from the standard:

这种复制/移动操作的省略,称为复制省略,在以下情况下是允许的(可以结合起来消除多个副本):

(1.1) - 在具有类返回类型的函数的 return 语句中,当表达式是非易失性自动对象的名称时(函数参数或由异常声明引入的变量除外handler ([except.handle])) 与函数返回类型相同(忽略cv-qualification),可以通过将自动对象直接构造到函数调用的返回对象中来省略复制/移动操作

所以要求是

  1. 在返回语句中
  2. 在函数中
  3. 具有类返回类型
  4. 当表达式是非易失性自动对象的名称时(函数参数或由处理程序的异常声明引入的变量 ([except.handle]) 除外)
  5. 与函数返回类型具有相同类型(忽略 cv 限定)

我认为要求 1、2、3 和 5 已满足,但要求 4 未得到满足。 (a) 不是对象的名称。因此,对于给定的代码,复制省略不适用。由于 move-constructor 有副作用,因此在 as-if 规则下也不能省略。

因此 gcc 是正确的,而 Visual Studio(和 clang)在这里是错误的。

【讨论】:

    【解决方案2】:

    GCC 是对的。

    根据[class.copy.elision] paragraph 1:

    这种复制/移动操作的省略,称为复制省略,在以下情况下是允许的(可以结合起来消除多个副本):

    • 在具有类返回类型的函数中的return 语句中,当表达式是非易失性自动对象的名称(函数除外) handler ([except.handle])) 的 exception-declaration 引入的参数或变量,其类型与函数返回类型相同(忽略 cv 限定) , 可以通过将自动对象直接构造到函数调用的返回对象中来省略复制/移动操作

    • ...

    return 语句中带括号的表达式不符合复制省略的条件。

    事实上,直到CWG 1597 的决议,return 语句中的括号 id-expression 甚至不能被视为执行移动的右值。

    【讨论】:

    • 但是为什么不符合条件呢?只是因为名字在括号里?这只是一个简单的规则吗?在某处我读到表达式的评估将导致一个左值,但我不知道这个。最后我觉得很奇怪
    猜你喜欢
    • 1970-01-01
    • 2018-09-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-06-02
    • 1970-01-01
    相关资源
    最近更新 更多