4

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

示例代码:

#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
4

2 回答 2

5

这是标准中的相关引用

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

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

所以要求是

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

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

因此 gcc 是正确的,而视觉工作室(和clang)在这里是错误的。

于 2018-02-12T16:32:20.477 回答
4

海合会是对的。

根据[class.copy.elision] 第 1 段

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

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

  • ...

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

事实上,在CWG 1597决议之前,语句中带括号的 id-expressionreturn甚至不能被视为执行移动的右值。

于 2018-02-12T16:37:52.827 回答