6

考虑以下程序:

#include<iostream>
using namespace std;

struct S
{
    S() = default;
    S(const S& other) = delete;
    S(S&& other) = delete;
    int i;
};

S nakedBrace()
{
     return {}; // no S constructed here?
}

S typedBrace()
{
     return S{};
}

int main()
{
    // produce an observable effect.
    cout << nakedBrace().i << endl; // ok
    cout << typedBrace().i << endl; // error: deleted move ctor
}

示例会话:

$ g++ -Wall -std=c++14 -o no-copy-ctor no-copy-ctor.cpp
no-copy-ctor.cpp: In function 'S typedBrace()':
no-copy-ctor.cpp:19:12: error: use of deleted function 'S::S(S&&)'
   return S{};
            ^
no-copy-ctor.cpp:8:5: note: declared here
     S(S&& other) = delete;

gcc 接受让我感到惊讶nakedBrace()。我认为从概念上讲这两个函数是等效的:S构造并返回一个临时函数。可能会或可能不会执行复制省略,但按照标准 (12.8/32) 的要求,移动或复制 ctor(两者都在此处删除)仍然必须是可访问的。

这是否意味着nakedBrace()永远不会构造 S?或者确实如此,但直接在带有大括号初始化的返回值中,因此在概念上不需要复制移动/ctor?

4

2 回答 2

4

这是标准行为。

N4140 [stmt.return]/2:[...] 带有花括号初始化列表的 return 语句通过指定初始化列表中的复制列表初始化(8.5.4)初始化要从函数返回的对象或引用。[...]

这意味着由nakedBrace和执行的初始化typedBrace等价于:

S nakedBrace = {}; //calls default constructor
S typedBrace = S{}; //calls default, then copy constructor (likely elided)
于 2016-01-27T15:44:14.387 回答
1

[stmt.return]/2 ...带有非void类型表达式的return语句只能在返回值的函数中使用;表达式的值返回给函数的调用者。表达式的值被隐式转换为它出现的函数的返回类型。return 语句可能涉及临时对象的构造和复制或移动 (12.2)... 带有花括号初始化列表的 return 语句通过复制列表初始化 (8.5) 初始化要从函数返回的对象或引用.4) 从指定的初始化列表中。

[class.temporary]/1类类型的临时对象是在各种上下文中创建的:...返回纯右值(6.6.3)...

所以是的,据我所知,存在语义差异。typedBrace计算一个表达式S{},它产生一个类型的纯右值S,然后尝试从该表达式复制构造它的返回值。nakedBrace而是直接从braced-init-list 构造它的返回值。

这与S s{};(有效)与S s = S{};(无效)的情况相同,只是被某种程度的间接所掩盖。

于 2016-01-27T15:44:17.227 回答