临时的复制列表初始化与直接列表初始化
copy list initialization vs direct list initialization of temporary
给定以下结构:
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;}
};
输出:
std::pair<std::string, ABC> myPair{{}, {}};
是:
ABC
copy
~ABC
~ABC
同时输出:
std::pair<std::string, ABC> myPair{{}, ABC{}};
是:
ABC
move
~ABC
~ABC
在试图理解两者之间的区别时,我想我已经确定第一种情况是使用复制列表初始化,而第二种情况使用未命名临时文件(编号 7 和 2)的直接列表初始化,分别在这里:http://en.cppreference.com/w/cpp/language/list_initialization).
搜索类似问题我发现了这个:Why does the standard differentiate between direct-list-initialization and copy-list-initialization? and this: Does copy list initialization invoke copy ctor conceptually?。
这些问题的答案讨论了这样一个事实,即对于复制列表初始化,使用显式构造函数会导致代码格式错误。事实上,如果我使 ABC 的默认构造函数显式化,我的第一个示例将无法编译,但那(也许)是另一回事。
那么,问题来了:为什么第一种情况下临时复制,第二种情况下移动?在复制列表初始化的情况下是什么阻止它被移动?
作为说明,以下代码:
std::pair<std::string, ABC> myPair = std::make_pair<string, ABC>({}, {});
也会导致调用 ABC 的移动构造函数(并且不会调用复制构造函数),但可能涉及不同的机制。
您可以在以下位置试用代码(在 C++14 模式下使用 gcc-4.9.2):https://ideone.com/Kc8xIn
一般来说,braced-init-lists 和 {}
一样不是表达式,也没有类型。如果你有函数模板
template<typename T> void f(T);
并调用f( {} )
,T
不会推导类型,推导失败。
另一方面,ABC{}
是 ABC
类型的纯右值表达式("explicit type conversion in functional notation")。对于像 f( ABC{} )
这样的调用,函数模板可以从这个表达式中推导出类型 ABC
。
在 C++14 以及 C++11 中,std::pair
具有以下构造函数 [pairs.pair]; T1
和T2
是std::pair
class模板的模板参数名称:
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...>);
注意有一个构造函数
constexpr pair(const T1& x, const T2& y); // (C)
但是没有
constexpr pair(T1&& x, T2&& y);
反而有完美转发
template<class U, class V> constexpr pair(U&& x, V&& y); // (P)
如果您尝试使用两个初始化器初始化一个 std::pair
,其中至少一个是 braced-init-list,则构造函数 (P) 不是可行,因为它无法推断其模板参数。
(C) 不是构造函数模板。它的参数类型 T1 const&
和 T2 const&
由 class 模板参数固定。可以从空的 braced-init-list 初始化对常量类型的引用。这将创建一个绑定到引用的临时对象。由于引用的类型是 const,(C) 构造函数会将其参数复制到 class' 数据成员中。
当您通过 std::pair<T,U>{ T{}, U{} }
初始化一对时,T{}
和 U{}
是纯右值表达式。构造函数模板(P)可以推断出它们的类型并且是可行的。类型推导后产生的实例化比 (C) 构造函数更匹配,因为 (P) 将产生右值引用参数并将纯右值参数绑定到它们。 (C) 另一方面将纯右值参数绑定到左值引用。
为什么当通过 std::pair<T,U>{ {}, U{} }
调用时,实例会移动第二个参数?
libstdc++ 定义了额外的构造函数。下面是 78536ab78e 的 std::pair
实现的摘录,省略了函数定义、一些注释和 SFINAE。 _T1
和_T2
是std::pair
class模板的模板参数名称。
_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...>);
注意 (E) 构造函数模板:它将复制第一个参数并完美转发第二个参数。对于像 std::pair<T,U>{ {}, U{} }
这样的初始化,它是可行的,因为它只需要从第二个参数推导出一个类型。对于第二个参数,它也比 (C) 更匹配,因此总体上更匹配。
"DR 811" 注释在 libstdc++ 源代码中。它指的是 LWG DR 811 添加了一些 SFINAE,但没有新的构造函数。
构造函数 (E) 和 (X) 是 libstdc++ 扩展。不过,我不确定它是否合规。
另一方面,libc++ 没有这个额外的构造函数。例如 std::pair<T,U>{ {}, U{} }
,它将 copy the second argument。
给定以下结构:
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;}
};
输出:
std::pair<std::string, ABC> myPair{{}, {}};
是:
ABC
copy
~ABC
~ABC
同时输出:
std::pair<std::string, ABC> myPair{{}, ABC{}};
是:
ABC
move
~ABC
~ABC
在试图理解两者之间的区别时,我想我已经确定第一种情况是使用复制列表初始化,而第二种情况使用未命名临时文件(编号 7 和 2)的直接列表初始化,分别在这里:http://en.cppreference.com/w/cpp/language/list_initialization).
搜索类似问题我发现了这个:Why does the standard differentiate between direct-list-initialization and copy-list-initialization? and this: Does copy list initialization invoke copy ctor conceptually?。
这些问题的答案讨论了这样一个事实,即对于复制列表初始化,使用显式构造函数会导致代码格式错误。事实上,如果我使 ABC 的默认构造函数显式化,我的第一个示例将无法编译,但那(也许)是另一回事。
那么,问题来了:为什么第一种情况下临时复制,第二种情况下移动?在复制列表初始化的情况下是什么阻止它被移动?
作为说明,以下代码:
std::pair<std::string, ABC> myPair = std::make_pair<string, ABC>({}, {});
也会导致调用 ABC 的移动构造函数(并且不会调用复制构造函数),但可能涉及不同的机制。
您可以在以下位置试用代码(在 C++14 模式下使用 gcc-4.9.2):https://ideone.com/Kc8xIn
一般来说,braced-init-lists 和 {}
一样不是表达式,也没有类型。如果你有函数模板
template<typename T> void f(T);
并调用f( {} )
,T
不会推导类型,推导失败。
另一方面,ABC{}
是 ABC
类型的纯右值表达式("explicit type conversion in functional notation")。对于像 f( ABC{} )
这样的调用,函数模板可以从这个表达式中推导出类型 ABC
。
在 C++14 以及 C++11 中,std::pair
具有以下构造函数 [pairs.pair]; T1
和T2
是std::pair
class模板的模板参数名称:
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...>);
注意有一个构造函数
constexpr pair(const T1& x, const T2& y); // (C)
但是没有
constexpr pair(T1&& x, T2&& y);
反而有完美转发
template<class U, class V> constexpr pair(U&& x, V&& y); // (P)
如果您尝试使用两个初始化器初始化一个 std::pair
,其中至少一个是 braced-init-list,则构造函数 (P) 不是可行,因为它无法推断其模板参数。
(C) 不是构造函数模板。它的参数类型 T1 const&
和 T2 const&
由 class 模板参数固定。可以从空的 braced-init-list 初始化对常量类型的引用。这将创建一个绑定到引用的临时对象。由于引用的类型是 const,(C) 构造函数会将其参数复制到 class' 数据成员中。
当您通过 std::pair<T,U>{ T{}, U{} }
初始化一对时,T{}
和 U{}
是纯右值表达式。构造函数模板(P)可以推断出它们的类型并且是可行的。类型推导后产生的实例化比 (C) 构造函数更匹配,因为 (P) 将产生右值引用参数并将纯右值参数绑定到它们。 (C) 另一方面将纯右值参数绑定到左值引用。
为什么当通过 std::pair<T,U>{ {}, U{} }
调用时,实例会移动第二个参数?
libstdc++ 定义了额外的构造函数。下面是 78536ab78e 的 std::pair
实现的摘录,省略了函数定义、一些注释和 SFINAE。 _T1
和_T2
是std::pair
class模板的模板参数名称。
_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...>);
注意 (E) 构造函数模板:它将复制第一个参数并完美转发第二个参数。对于像 std::pair<T,U>{ {}, U{} }
这样的初始化,它是可行的,因为它只需要从第二个参数推导出一个类型。对于第二个参数,它也比 (C) 更匹配,因此总体上更匹配。
"DR 811" 注释在 libstdc++ 源代码中。它指的是 LWG DR 811 添加了一些 SFINAE,但没有新的构造函数。
构造函数 (E) 和 (X) 是 libstdc++ 扩展。不过,我不确定它是否合规。
另一方面,libc++ 没有这个额外的构造函数。例如 std::pair<T,U>{ {}, U{} }
,它将 copy the second argument。