如何调用std :: vector中包含的对象的构造函数?

Pie*_*tro 11 c++ static constructor stl stdvector

当我创建一个std :: vector对象时,并不总是调用这些对象的构造函数.

#include <iostream>
#include <vector>
using namespace std;

struct C {
    int id;
    static int n;
    C() { id = n++; }   // not called
//  C() { id = 3; }     // ok, called
};

int C::n = 0;


int main()
{
    vector<C> vc;

    vc.resize(10);

    cout << "C::n = " << C::n << endl;

    for(int i = 0; i < vc.size(); ++i)
        cout << i << ": " << vc[i].id << endl;  
}
Run Code Online (Sandbox Code Playgroud)

这是我得到的输出:

C::n = 1
0: 0
1: 0
2: 0
...
Run Code Online (Sandbox Code Playgroud)

这就是我想要的:

C::n = 10
0: 0
1: 1
2: 2
...
Run Code Online (Sandbox Code Playgroud)

在这个例子中,我是否被迫调整向量的大小,然后"手动"初始化它的元素?
可能的原因是向量的元素没有按照有序的方式初始化,从第一个到最后一个,因此我无法获得确定性行为?

我想要做的是,轻松地计算程序中创建的对象数量,在不同的容器中,在代码的不同点,以及为每个对象提供单个id.

谢谢!

Jam*_*lis 21

并不总是调用这些对象的构造函数.

是的,它是,但它不是你想的构造函数.成员函数resize()实际上是这样声明的:

void resize(size_type sz, T c = T());
Run Code Online (Sandbox Code Playgroud)

第二个参数是要复制到向量的每个新插入元素的对象.如果省略第二个参数,则默认构造一个类型的对象,T然后将该对象复制到每个新元素中.

在您的代码中,C构造一个临时构造并调用默认构造函数; id然后将隐式声明的复制构造函数调用十次(将十个元素插入到向量中),并且向量中的所有元素都具有相同的id.

[注意那些感兴趣的人:在C++ 03中,resize()(c)的第二个参数是按值获取的; 在C++ 0x中,它由const lvalue引用(参见LWG缺陷679)].

在这个例子中,我是否被迫调整向量的大小,然后"手动"初始化它的元素?

您可以(也可能应该)将元素单独插入到矢量中,例如,

std::vector<C> vc;
for (unsigned i(0); i < 10; ++i)
    vc.push_back(C());
Run Code Online (Sandbox Code Playgroud)


sje*_*397 5

原因是vector :: resize通过调用自动提供的复制构造函数而不是您在示例中定义的构造函数来插入副本.

为了获得所需的输出,您可以显式定义复制构造函数:

struct C {
//....
C(const C& other) {
    id = n++;
    // copy other data members
}
//....
};
Run Code Online (Sandbox Code Playgroud)

由于vector :: resize的工作方式(它有第二个可选参数用作它创建的副本的'prototype',在你的情况下使用默认值C()),这会在你的例子中创建11个对象('原型' '和10份副本).

编辑(在许多评论中包含一些好的建议):

这个解决方案有几个缺点值得注意,以及一些可能产生更易维护和合理代码的选项和变体.

  • 这种方法确实增加了维护成本和风险.每当添加或删除类的成员变量时,您都必须记住修改复制构造函数.如果依赖于默认的复制构造函数,则不必执行此操作.解决这个问题的一种方法是将计数器封装在另一个类中(像这样),这也可以说是更好的OO设计,但当然你还必须记住多重继承可能出现的许多问题.

  • 它可以让其他人更难理解,因为副本不再是大多数人所期望的.同样,处理您的类(包括标准容器)的其他代码可能会出错.解决这个问题的一种operator==方法是为你的类定义一个方法(可能有人认为,当覆盖复制构造函数时,这是一个好主意,即使你不使用该方法),以保持它在概念上"合理",并且作为一种内部文件.如果您的类有很多用处,您可能最终会提供一个,operator=以便您可以保持自动生成的实例ID与应在此运算符下发生的类成员分配的分离.等等 ;)

  • 如果您对程序有足够的控制权来使用动态创建的实例(通过new)并使用指向容器内部的指针,它可能会消除"副本的不同id值"的整个问题的歧义.这确实意味着你需要在某种程度上"手动"初始化元素 - 但编写一个函数可以让你返回一个充满指向新的初始化实例的指针的函数并不是很多工作.如果在使用标准容器时始终处理指针,则不必担心标准容器会在"封面"下创建任何实例.

如果您意识到所有这些问题,并且相信您可以应对后果(当然高度依赖于您的特定上下文),那么覆盖复制构造函数是一个可行的选择.毕竟,语言功能是有原因的.显然,它并不像看起来那么简单,你应该小心.

  • 这不行.复制构造函数需要复制; 如果不是这个类不能在标准容器中使用.我厌倦了制作一个实际上没有复制任何东西的复制构造函数. (3认同)