复制列表初始化与临时列表初始化临时

Hug*_*ugo 5 move temporary-objects c++11 list-initialization

给定以下结构:

struct ABC
{
    ABC(){cout << "ABC" << endl;}
    ~ABC() noexcept {cout << "~ABC" << endl;}
    ABC(ABC const&) {cout << "copy" << endl;}
    ABC(ABC&&) noexcept {cout << "move" << endl;}
    ABC& operator=(ABC const&){cout << "copy=" << endl;}
    ABC& operator=(ABC&&) noexcept {cout << "move=" << endl;}
};
Run Code Online (Sandbox Code Playgroud)

输出:

std::pair<std::string, ABC> myPair{{}, {}};
Run Code Online (Sandbox Code Playgroud)

是:

ABC
copy
~ABC
~ABC
Run Code Online (Sandbox Code Playgroud)

而输出:

std::pair<std::string, ABC> myPair{{}, ABC{}};
Run Code Online (Sandbox Code Playgroud)

是:

ABC
move
~ABC
~ABC
Run Code Online (Sandbox Code Playgroud)

在试图理解两者之间的差异时,我认为我已经确定第一种情况是使用复制列表初始化,而第二种情况使用未命名临时的直接列表初始化(数字7和2分别在这里) :http://en.cppreference.com/w/cpp/language/list_initialization).

搜索类似的问题我发现:为什么标准区分直接列表初始化和复制列表初始化?并且:复制列表初始化是否从概念上调用了复制ctor?.

这些问题的答案讨论了这样一个事实:对于复制列表初始化,使用显式构造函数会使代码格式错误.事实上,如果我将ABC的默认构造函数显式化,我的第一个例子将不会编译,但这可能是(可能)另一个问题.

所以,问题是:为什么临时复制在第一种情况下,但在第二种情况下移动?什么阻止它在复制列表初始化的情况下被移动?

作为注释,以下代码:

std::pair<std::string, ABC> myPair = std::make_pair<string, ABC>({}, {});
Run Code Online (Sandbox Code Playgroud)

还导致调用ABC的移动构造函数(并且没有复制构造函数调用),但可能涉及不同的机制.

您可以在以下网址尝试输出代码(在C++ 14模式下使用gcc-4.9.2):https://ideone.com/Kc8xIn

dyp*_*dyp 8

在一般情况下,支撑-INIT-列表一样{}都没有表达,没有一个类型.如果你有一个功能模板

template<typename T> void f(T);
Run Code Online (Sandbox Code Playgroud)

并且呼叫f( {} ),不会推断出任何类型T,并且类型扣除将失败.

另一方面,ABC{}是类型的prvalue表达式ABC("功能表示法中的显式类型转换").对于类似的调用f( ABC{} ),函数模板可以ABC从此表达式中推断出类型.


在C++ 14中,以及在C++ 11中,std::pair有以下构造函数[pairs.pair]; T1并且T2std::pair类模板的模板参数的名称:

pair(const pair&) = default;
pair(pair&&) = default;
constexpr pair();
constexpr pair(const T1& x, const T2& y);
template<class U, class V> constexpr pair(U&& x, V&& y);
template<class U, class V> constexpr pair(const pair<U, V>& p);
template<class U, class V> constexpr pair(pair<U, V>&& p);
template <class... Args1, class... Args2>
pair(piecewise_construct_t, tuple<Args1...>, tuple<Args2...>);
Run Code Online (Sandbox Code Playgroud)

请注意,有一个构造函数

constexpr pair(const T1& x, const T2& y); // (C)
Run Code Online (Sandbox Code Playgroud)

但不是

constexpr pair(T1&& x, T2&& y);
Run Code Online (Sandbox Code Playgroud)

相反,有一个完美的转发

template<class U, class V> constexpr pair(U&& x, V&& y); // (P)
Run Code Online (Sandbox Code Playgroud)

如果您尝试std::pair使用两个初始化程序初始化a ,其中至少有一个是braced-init-list,则构造函数(P)不可行,因为它无法推导出其模板参数.

(C)不是构造函数模板.它的参数类型T1 const&,并T2 const&通过类模板参数固定.可以从空的braced-init-list初始化对常量类型的引用.这将创建一个绑定到引用的临时对象.由于引用的类型是const,(C)构造函数会将其参数复制到类的数据成员中.


当你通过初始化一对时std::pair<T,U>{ T{}, U{} },T{}U{}是prvalue表达式.构造函数模板(P)可以推断出它们的类型并且是可行的.类型推导后生成的实例化比(C)构造函数更好地匹配,因为(P)将生成rvalue-reference参数并将prvalue参数绑定到它们.另一方面,(C)将prvalue参数绑定到lvalue-references.


那么为什么实例会在调用via时移动第二个参数std::pair<T,U>{ {}, U{} }

libstdc ++定义了其他构造函数.以下是std::pair78536ab78e的实现摘要,省略了函数定义,一些注释和SFINAE._T1并且_T2std::pair类模板的模板参数的名称.

  _GLIBCXX_CONSTEXPR pair();

  _GLIBCXX_CONSTEXPR pair(const _T1& __a, const _T2& __b); // (C)

  template<class _U1, class _U2>
constexpr pair(const pair<_U1, _U2>& __p);

  constexpr pair(const pair&) = default;
  constexpr pair(pair&&) = default;

  // DR 811.
  template<class _U1>
constexpr pair(_U1&& __x, const _T2& __y); // (X)

  template<class _U2>
constexpr pair(const _T1& __x, _U2&& __y); // (E) <=====================

  template<class _U1, class _U2>
constexpr pair(_U1&& __x, _U2&& __y);      // (P)

  template<class _U1, class _U2>
constexpr pair(pair<_U1, _U2>&& __p);

  template<typename... _Args1, typename... _Args2>
    pair(piecewise_construct_t, tuple<_Args1...>, tuple<_Args2...>);
Run Code Online (Sandbox Code Playgroud)

注意(E)构造函数模板:它将复制第一个参数并完美地转发第二个参数.对于像这样的初始化std::pair<T,U>{ {}, U{} },它是可行的,因为它只需要从第二个参数中推导出一个类型.对于第二个参数,它也是比(C)更好的匹配,因此总体上更好地匹配.

"DR 811"注释位于libstdc ++源代码中.它指的是LWG DR 811增加了一些SFINAE,但没有新的构造函数.

构造函数(E)和(X)是libstdc ++扩展.不过,我不确定它是否合规.

另一方面,libc ++没有这个额外的构造函数.例如std::pair<T,U>{ {}, U{} },它将复制第二个参数.

两个库实现的现场演示