gpt4 book ai didi

c++ - 这个 C++ 代码在技术上会发生什么?

转载 作者:IT老高 更新时间:2023-10-28 14:00:02 26 4
gpt4 key购买 nike

我有一个类 B,其中包含类 A 的 vector 。我想通过构造函数初始化这个 vector 。 A 类输出一些调试信息,以便我可以看到它何时被构造、销毁、复制或移动。

#include <vector>
#include <iostream>

using namespace std;

class A {
public:
A() { cout << "A::A" << endl; }
~A() { cout << "A::~A" << endl; }
A(const A& t) { cout <<"A::A(A&)" << endl; }
A(A&& t) { cout << "A::A(A&&)" << endl; }
};

class B {
public:
vector<A> va;
B(const vector<A>& va) : va(va) {};
};

int main(void) {
B b({ A() });
return 0;
}

现在当我运行这个程序时(使用 GCC 选项 -fno-elide-constructors 编译,因此移动构造函数调用没有被优化掉)我得到以下输出:

A::A
A::A(A&&)
A::A(A&&)
A::A(A&)
A::A(A&)
A::~A
A::~A
A::~A
A::~A
A::~A

因此,编译器生成的不仅仅是 A 的一个实例,而是它的五个实例。 A 移动两次,复制两次。我没想到。 vector 通过引用传递给构造函数,然后复制到类字段中。所以我本来期望一个复制操作甚至只是一个移动操作(因为我希望我传递给构造函数的 vector 只是一个右值),而不是两个拷贝和两个移动。有人可以解释一下这段代码到底发生了什么吗?它在哪里以及为什么会创建所有这些 A 的拷贝?

最佳答案

以相反的顺序执行构造函数调用可能会有所帮助。

B b({ A() });

构建 B ,编译器必须调用 B 的构造函数,该构造函数接受 const vector<A>& .反过来,该构造函数必须制作 vector 的拷贝,包括其所有元素。这是您看到的第二个复制 ctor 调用。

构造要传递给B的临时 vector 的构造函数,编译器必须调用 initializer_list std::vector 的构造函数.反过来,该构造函数必须复制 initializer_list 中包含的内容。 *。这是您看到的第一个复制构造函数调用。

标准规定了initializer_list对象在 §8.5.4 [dcl.init.list]/p5:

中构造

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 const 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.

从相同类型的对象复制初始化使用重载解析来选择要使用的构造函数(第 8.5 节 [dcl.init]/p17),因此对于相同类型的右值,它将调用移动构造函数如果有的话。因此,构建 initializer_list<A>从花括号初始化器列表中,编译器将首先构造一个包含一个 const A 的数组通过从临时 A 移动由 A() build ,引起移动构造函数调用,然后构造 initializer_list对象来引用该数组。

不过,我不知道 g++ 中的另一个 Action 是从哪里来的。 initializer_list s 通常只不过是一对指针,标准要求复制一个指针不会复制底层元素。 g++ 似乎 call the move constructor twice创建 initializer_list 时从暂时的。它甚至 calls the move constructor在构建 initializer_list 时来自左值。

我最好的猜测是它在字面上实现了标准的非规范示例。该标准提供了以下示例:

struct X {
X(std::initializer_list<double> v);
};

X x{ 1,2,3 };

The initialization will be implemented in a way roughly equivalent to this:**

const double __a[3] = {double{1}, double{2}, double{3}};
X x(std::initializer_list<double>(__a, __a+3));

assuming that the implementation can construct an initializer_list object with a pair of pointers.

所以如果你从字面上理解这个例子,initializer_list 下的数组在我们的例子中,将被构造为:

const A __a[1] = { A{A()} };

这确实会导致两次移动构造函数调用,因为它构造了一个临时 A , 复制初始化第二个临时 A从第一个开始,然后从第二个临时复制初始化数组成员。然而,标准的规范性文本清楚地表明应该只有一个复制初始化,而不是两个,所以这似乎是一个错误。

最后,第一个A::A直接来自A() .

没有太多关于析构函数调用的讨论。在 b 的构建过程中创建的所有临时对象(无论数量)将在语句的末尾以相反的构造顺序被破坏,而一个 A存储在 b将在 b 时被破坏超出范围。


* initializer_list标准库容器的构造函数被定义为等效于调用构造函数,该构造函数使用两个迭代器 list.begin()list.end() .这些成员函数返回 const T* ,所以它不能被移动。在 C++14 中,后备数组是 const ,因此更清楚的是,您不可能从它移开或以其他方式更改它。

** 这个答案最初引用了 N3337(C++11 标准加上一些小的编辑更改),其中数组的元素类型为 E而不是 const E示例中的数组类型为 double .在 C++14 中,底层数组是 const由于CWG 1418 .

关于c++ - 这个 C++ 代码在技术上会发生什么?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/24752830/

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