我正在尝试使用{}列表进行一些测试.当我在VS2015中编译它时,输出是
Run Code Online (Sandbox Code Playgroud)copy A 0
只是不明白,复制构造函数在哪里调用?
#include <iostream>
struct A
{
A() = default;
A(int i) : m_i(i) {}
A(const A& a)
{
std::cout << "copy A " << m_i << std::endl;
}
int m_i = 0;
};
struct B
{
B(const A& a) : m_a(a) {}
B(const A& a1, const A& a2) {}
A m_a;
};
int main()
{
A a1{1}, a2{2};
B b({ a1, a2 });
return 0;
}
Run Code Online (Sandbox Code Playgroud)
dyp*_*dyp 15
精简版:
在直接初始化中B b({a1, a2}),braced-init-list {a1, a2}被认为是构造函数的一个参数B.此参数{a1, a2}将用于初始化构造函数的第一个参数.B包含一个隐式声明的构造函数B(B const&).B const&可以{a1, a2}通过创建临时B来初始化引用.此临时包含一个A子对象,该子对象最终将b.m_a通过B(B const&)复制构造函数复制到该子对象.
相比于:
void foo(B const& b0);
foo({a1, a2}); // one argument, creates a temporary `B`
Run Code Online (Sandbox Code Playgroud)
我们不会看到任何复制表单的初始化,B b{a1, a2}也B b(a1, a2)不会B b = {a1, a2},因为这些情况考虑a1和a2作为(单独的)参数 - 除非std::initializer_list存在可行的构造函数.
长版:
该类B包含以下构造函数:
B(const A& a) : m_a(a) {} // #0
B(const A& a1, const A& a2) {} // #1
B(B const&) = default; // implicitly declared #2
B(B&&) = default; // implicitly declared #3
Run Code Online (Sandbox Code Playgroud)
#3由于缺乏对隐式提供的特殊移动功能的支持,VS2013中不会出现这种情况.OP的程序中没有使用#0.
初始化B b({a1, a2})必须选择其中一个构造函数.我们只提供一个参数{a1, a2},因此#1不可行.#0也不可行,因为A不能用两个参数构造.#2和#3都仍然可行(VS2013中不存在#3).
现在,重载决策尝试初始化a B const&或B&&from {a1, a2}.B将创建临时值并绑定到此引用.如果存在#3,则过载分辨率将优先选择#3至#2.
临时的创建再次查看上面显示的四个构造函数,但现在我们有两个参数a1和a2(或者一个initializer_list,但这里不相关).#1是唯一可行的重载,临时是通过创建的B(const A& a1, const A& a2).
所以,我们基本上最终会结束B b( B{a1, a2} ).从临时B{a1, a2}到复制(或移动)b可以省略(复制 - 省略).这就是为什么g ++以及铛++不调用拷贝构造函数,也不既不移动构造函数B,也没有A.
VS2013似乎没有在这里删除复制构造,并且它不能移动构造,因为它不能隐含地提供#3(VS2015将修复它).因此,VS2013调用B(B const&),复制B{a1, a2}.m_a到b.m_a.这会调用A复制构造函数.
如果#3存在,并且移动未被删除,则调用隐式声明的移动构造函数#3.由于A具有显式声明的复制构造函数,因此不会隐式声明任何移动构造函数A.这也导致了一份从建设B{a1, a2}.m_a到b.m_a,但通过的移动构造函数B.
在VS2013,如果我们手动添加一个移动构造函数来A和B,我们会发现,A将被移动而不是复制:
#include <iostream>
#include <utility>
struct A
{
A() = default;
A(int i) : m_i(i) {}
A(const A& a)
{
std::cout << "copy A " << m_i << std::endl;
}
A(A&& a)
{
std::cout << "move A " << m_i << std::endl;
}
int m_i = 0;
};
struct B
{
//B(const A& a) : m_a(a) {}
B(const A& a1, const A& a2) {}
B(B const&) = default;
B(B&& b) : m_a(std::move(b.m_a)) {}
A m_a;
};
Run Code Online (Sandbox Code Playgroud)
通过跟踪每个构造函数来理解这些程序通常更容易.使用MSVC特定的__FUNCSIG__(g ++/clang ++可以使用__PRETTY_FUNCTION__):
#include <iostream>
#define PRINT_FUNCSIG() { std::cout << __FUNCSIG__ << "\n"; }
struct A
{
A() PRINT_FUNCSIG()
A(int i) : m_i(i) PRINT_FUNCSIG()
A(const A& a) : m_i(a.m_i) PRINT_FUNCSIG()
int m_i = 0;
};
struct B
{
B(const A& a1, const A& a2) PRINT_FUNCSIG()
B(B const& b) : m_a(b.m_a) PRINT_FUNCSIG()
A m_a;
};
int main()
{
A a1{1}, a2{2};
B b({ a1, a2 });
return 0;
}
Run Code Online (Sandbox Code Playgroud)
这打印(没有评论):
__thiscall A::A(int) // a1{1}
__thiscall A::A(int) // a2{2}
__thiscall A::A(void) // B{a1, a2}.m_a, default-constructed
__thiscall B::B(const struct A &,const struct A &) // B{a1, a2}
__thiscall A::A(const struct A &) // b.m_a(B{a1, a2}.m_a)
__thiscall B::B(const struct B &) // b(B{a1, a2})
附加的事实:
B b(B{a1, a2});原件的复制结构B b({a1, a2}).