gpt4 book ai didi

c++ - 何时为此结构调用复制构造函数?

转载 作者:IT老高 更新时间:2023-10-28 22:59:24 27 4
gpt4 key购买 nike

我正在尝试使用 {}-lists 进行一些测试。当我在 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;
}

最佳答案

短版:

在像 B b({a1, a2}) 这样的直接初始化中,braced-init-list {a1, a2} 被视为 B 的构造函数的 一个 参数。此参数 {a1, a2} 将用于初始化构造函数的第一个参数。 B 包含一个隐式声明的构造函数 B(B const&)。一个引用 B const& 可以通过创建一个 临时 B{a1, a2} 初始化。这个临时包含一个 A 子对象,这个子对象最终会通过 B(B const&) 复制构造函数复制到 b.m_a

比较:

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},因为这些情况将 a1a2 视为(单独的)参数 - 除非可行的 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

#3 将不会出现在 VS2013 中,因为缺乏对隐式提供的特殊移动功能的支持。 #0 没有在 OP 的程序中使用。

初始化B b({a1, a2}) 必须选择其中一个构造函数。我们只提供一个参数{a1, a2},所以#1 是不可行的。 #0 也不可行,因为 A 不能由两个参数构成。 #2 和 #3 仍然可行(#3 在 VS2013 中不存在)。

现在,重载解析尝试从 {a1, a2} 初始化 B const&B&&。将创建一个临时 B 并将其绑定(bind)到此引用。如果 #3 存在,重载解析会优先选择 #3 而不是 #2。

临时的创建再次查看上面显示的四个构造函数,但现在我们有两个参数a1a2(或initializer_list,但这在这里无关紧要)。 #1 是唯一可行的重载,临时是通过 B(const A& a1, const A& a2) 创建的。

所以,我们基本上以 B b( B{a1, a2} ) 告终。从临时 B{a1, a2}b 的复制(或移动)可以被省略(复制省略)。这就是为什么 g++ 和 clang++ 不调用 BA 的复制 ctor 或移动 ctor。

VS2013 似乎没有在这里省略复制构造,并且它不能移动构造,因为它不能隐式提供#3(VS2015 将解决这个问题)。因此,VS2013调用B(B const&),将B{a1, a2}.m_a复制到b.m_a。这会调用 A 的复制构造函数。

如果#3 存在,并且没有省略移动,则调用隐式声明的移动构造函数#3。由于 A 具有显式声明的复制构造函数,因此不会为 A 隐式声明移动构造函数。这也导致了从 B{a1, a2}.m_ab.m_a 的复制构造,但通过 B 的移动 ctor。


在 VS2013 中,如果我们手动将移动 ctor 添加到 AB,我们会注意到 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;
};

通过跟踪 every 构造函数通常更容易理解此类程序。使用 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;
}

此打印(不含评论):

__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})

其他事实:

  • VS2015 和 VS2013 do 都省略了 B b(B{a1, a2}); 的复制构造,但没有省略原始 B b({a1 , a2}).

关于c++ - 何时为此结构调用复制构造函数?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/30957087/

27 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com