我正在尝试使用{}列表进行一些测试。当我在VS2015中编译它时,输出是
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;
}
答案 0 :(得分:15)
简短版本:
在B b({a1, a2})
之类的直接初始化中, braced-init-list {a1, a2}
被视为{的一个参数{ {1}}。此参数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`
,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
将不会出现在VS2013中。 OP的程序中没有使用#0。
初始化#3
必须选择其中一个构造函数。我们只提供一个参数B b({a1, a2})
,因此#1不可行。 #0也不可行,因为{a1, a2}
不能从两个参数构造。 #2和#3都仍然可行(#3在VS2013中不存在)。
现在,重载决策尝试从A
初始化B const&
或B&&
。将创建临时{a1, a2}
并将其绑定到此引用。如果#3存在,则过载分辨率将优先选择#3至#2。
临时创建再次查看上面显示的四个构造函数,但现在我们有两个参数B
和a1
(或a2
,但这与此无关)。 #1是唯一可行的重载,临时是通过initializer_list
创建的。
因此,我们基本上以B(const A& a1, const A& a2)
为基础。从临时B b( B{a1, a2} )
到B{a1, a2}
的复制(或移动)可以省略(copy-elision)。这就是为什么g ++和clang ++不会调用副本ctor,也不会调用b
和B
的移动ctor。
VS2013似乎没有在这里删除复制构造,并且它不能移动构造,因为它不能隐含地提供#3(VS2015将修复它)。因此,VS2013会调用A
,将B(B const&)
复制到B{a1, a2}.m_a
。这会调用b.m_a
的复制构造函数。
如果#3存在,并且移动未被删除,则调用隐式声明的移动构造函数#3。由于A
具有显式声明的复制构造函数,因此不会为A
隐式声明移动构造函数。这也导致从A
到B{a1, a2}.m_a
的复制构造,但是通过b.m_a
的移动ctor。
在VS2013中,如果我们手动将移动广告添加到B
和A
,我们会注意到B
将被移动而不是复制:
A
通过跟踪每个构造函数来理解此类程序通常更容易。使用特定于MSVC的#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;
};
(g ++ / clang ++可以使用__FUNCSIG__
):
__PRETTY_FUNCTION__
打印(没有评论):
__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})
其他事实:
#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;
}
的副本构建,但不包括原始B b(B{a1, a2});
的副本构建。