我有一个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一起编译,因此move构造函数调用没有得到优化),我得到以下输出:
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 通过引用传递给构造函数,然后复制到class字段中。所以我本来希望有一个复制操作,甚至只是一个移动操作(因为我希望传递给构造函数的 vector 只是一个右值),而不是两个副本和两个移动。有人可以解释一下这段代码中发生了什么吗?它在哪里以及为什么在其中创建了所有这些A副本?

最佳答案

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

B b({ A() });

要构造B,编译器必须调用B的采用const vector<A>&的构造函数。该构造函数又必须复制 vector ,包括其所有元素。这是您看到的第二个副本ctor调用。

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

该标准规定了§8.5.4[dcl.init.list]/p5中initializer_list对象的构造方式:



从相同类型的对象中进行对象的复制初始化会使用重载分辨率来选择要使用的构造函数(第8.5节[dcl.init]/p17),因此,如果具有相同类型的右值,则将调用move构造函数可用。因此,为了从支撑初始化列表中构造initializer_list<A>,编译器将首先通过从由const A构造的临时A移出构造一个包含A()的数组,从而导致移动构造函数调用,然后构造initializer_list对象以引用该数组。

但是,我不知道g++的其他 Action 是从哪里来的。 initializer_list通常只不过是一对指针,并且标准规定,复制一个指针不能复制基础元素。从临时创建initializer_list时,g++似乎是call the move constructor twice。从左值构造initializer_list时,甚至calls the move constructor

我最好的猜测是它实际上是在执行标准的非规范示例。该标准提供以下示例:



因此,如果从字面上看这个例子,在我们的例子中,initializer_list底层的数组将被构造为:
const A __a[1] = { A{A()} };

确实会引起两次move构造函数调用,因为它构造了一个临时的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中,由于CWG 1418使得基础数组成为const

10-07 19:16
查看更多