为什么复制和移动构造函数一起调用?
Why are copy and move constructors called together?
考虑以下代码:
#include <iostream>
#include <vector>
using namespace std;
class A
{
public:
A(int) { cout << "int" << endl; }
A(A&&) { cout << "move" << endl; }
A(const A&) { cout << "copy" << endl; }
};
int main()
{
vector<A> v
{
A(10), A(20), A(30)
};
_getch();
return 0;
}
输出为:
int
int
int
copy
copy
copy
A(10)
、A(20)
和 A(30)
是临时的,对吧?
那么为什么要调用拷贝构造函数呢?难道不应该调用移动构造函数吗?
传递 move(A(10))
、move(A(20))
、move(A(30))
,输出为:
int
move
int
move
int
move
copy
copy
copy
在这种情况下,将调用复制或移动构造函数。
发生了什么事?
std::vector
可以从 std::initializer_list
构造,而您正在调用该构造函数。 initializer_list 构造规则声明此构造函数是积极首选的:
A constructor is an initializer-list constructor if its first parameter is of type std::initializer_list<E>
or reference to possibly cv-qualified std::initializer_list<E>
for some type E
, and either there are
no other parameters or else all other parameters have default arguments (8.3.6). [ Note: Initializer-list
constructors are favored over other constructors in list-initialization <...>]
此外,由于 initializer_list
作为在引擎盖下分配的数组的奇怪实现,std::initializer_list<E>
引用的相应数组的元素被强制复制初始化(可以省略):
An object of type std::initializer_list<E>
is constructed from an initializer list as if the implementation
allocated an array of N
elements of type E
, where N
is the number of elements in the initializer list.
Each element of that array is copy-initialized with the corresponding element of the initializer list, and
the std::initializer_list<E>
object is constructed to refer to that array
(以上两个参考资料均来自 N3337 [dcl.init.list])
但是,在您的第一个示例中,尽管名称为 ([dcl.init]/14),但副本 can/are 被删除了 ,因此您看不到额外的复制构造(它们也可以被移动) 你可以为此感谢你的编译器,因为 C++11 不需要 copy elision(尽管它在 C++17 中)。
有关详细信息,请参阅 [class.copy](“当满足某些条件时,允许实现省略 copy/move 构造 class
对象...”)。
最后部分是重点:
[support.initlist] 表示
An object of type initializer_list<E>
provides access to an array of objects of type const E
.
这意味着std::vector
不能直接接管内存;它必须被复制,这是你最终看到被调用的复制结构的地方。
在第二个示例中,正如 Kerrek SB 所说,您阻止了我之前提到的复制省略并导致了额外的移动开销。
A(10), A(20), A(30) are temporaries, right?
正确。
So why the copy constructor is called? Shouldn't the move constructor be called instead?
不幸的是,无法从 std::initializer_list
移动,这是 std::vector
的构造函数所使用的。
Passing move(A(10)), move(A(20)), move(A(30)) instead
In this case either copy or move constructor are called. What's happening?
因为 std::move
转换防止了复制省略,所以 std::initializer_list
的元素是在没有省略的情况下移动构造的。然后向量的构造函数从列表中复制。
考虑以下代码:
#include <iostream>
#include <vector>
using namespace std;
class A
{
public:
A(int) { cout << "int" << endl; }
A(A&&) { cout << "move" << endl; }
A(const A&) { cout << "copy" << endl; }
};
int main()
{
vector<A> v
{
A(10), A(20), A(30)
};
_getch();
return 0;
}
输出为:
int
int
int
copy
copy
copy
A(10)
、A(20)
和 A(30)
是临时的,对吧?
那么为什么要调用拷贝构造函数呢?难道不应该调用移动构造函数吗?
传递 move(A(10))
、move(A(20))
、move(A(30))
,输出为:
int
move
int
move
int
move
copy
copy
copy
在这种情况下,将调用复制或移动构造函数。
发生了什么事?
std::vector
可以从 std::initializer_list
构造,而您正在调用该构造函数。 initializer_list 构造规则声明此构造函数是积极首选的:
A constructor is an initializer-list constructor if its first parameter is of type
std::initializer_list<E>
or reference to possibly cv-qualifiedstd::initializer_list<E>
for some typeE
, and either there are no other parameters or else all other parameters have default arguments (8.3.6). [ Note: Initializer-list constructors are favored over other constructors in list-initialization <...>]
此外,由于 initializer_list
作为在引擎盖下分配的数组的奇怪实现,std::initializer_list<E>
引用的相应数组的元素被强制复制初始化(可以省略):
An object of type
std::initializer_list<E>
is constructed from an initializer list as if the implementation allocated an array ofN
elements of typeE
, whereN
is the number of elements in the initializer list. Each element of that array is copy-initialized with the corresponding element of the initializer list, and thestd::initializer_list<E>
object is constructed to refer to that array
(以上两个参考资料均来自 N3337 [dcl.init.list])
但是,在您的第一个示例中,尽管名称为 ([dcl.init]/14),但副本 can/are 被删除了 ,因此您看不到额外的复制构造(它们也可以被移动) 你可以为此感谢你的编译器,因为 C++11 不需要 copy elision(尽管它在 C++17 中)。
有关详细信息,请参阅 [class.copy](“当满足某些条件时,允许实现省略 copy/move 构造 class 对象...”)。
最后部分是重点:
[support.initlist] 表示
An object of type
initializer_list<E>
provides access to an array of objects of typeconst E
.
这意味着std::vector
不能直接接管内存;它必须被复制,这是你最终看到被调用的复制结构的地方。
在第二个示例中,正如 Kerrek SB 所说,您阻止了我之前提到的复制省略并导致了额外的移动开销。
A(10), A(20), A(30) are temporaries, right?
正确。
So why the copy constructor is called? Shouldn't the move constructor be called instead?
不幸的是,无法从 std::initializer_list
移动,这是 std::vector
的构造函数所使用的。
Passing move(A(10)), move(A(20)), move(A(30)) instead
In this case either copy or move constructor are called. What's happening?
因为 std::move
转换防止了复制省略,所以 std::initializer_list
的元素是在没有省略的情况下移动构造的。然后向量的构造函数从列表中复制。