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

C++ 如何调用std::vector中包含的对象的构造函数?,c++,stl,static,constructor,stdvector,C++,Stl,Static,Constructor,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 m

当我创建对象的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;  
}
这就是我想要的:

C::n = 10
0: 0
1: 1
2: 2
...
在本例中,我是否被迫调整向量大小,然后“手动”初始化其元素?
原因可能是向量的元素没有按顺序初始化,从第一个到最后一个,因此我无法获得确定性行为

我想做的是,轻松计算程序中、不同容器中、不同代码点中创建的对象数量,并为每个对象提供一个id

谢谢你

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

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

void resize(size_type sz, T c = T());
第二个参数是要复制到向量的每个新插入元素中的对象。如果省略第二个参数,它将默认构造一个
T
类型的对象,然后将该对象复制到每个新元素中

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

[请注意:在C++03中,
resize()
C
)的第二个参数按值取值;在C++0x中,它按常量左值引用取值(请参阅)]

在本例中,我是否被迫调整向量大小,然后“手动”初始化其元素

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

std::vector<C> vc;
for (unsigned i(0); i < 10; ++i)
    vc.push_back(C());
std::vector vc;
for(无符号i(0);i<10;++i)
vc.推回(C());
原因是通过调用自动提供的复制构造函数而不是您在示例中定义的构造函数来插入副本

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

struct C {
//....
C(const C& other) {
    id = n++;
    // copy other data members
}
//....
};
但是,由于vector::resize的工作方式(它有第二个可选参数用作它创建的副本的“原型”,在您的示例中使用默认值
C()
),因此在您的示例中创建了11个对象(“原型”和10个副本)

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

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

  • 这种方法确实增加了维护成本和风险。无论何时添加或删除类的成员变量,都必须记住修改复制构造函数。如果依赖默认的复制构造函数,则不必这样做。解决这个问题的一种方法是将计数器封装在另一个类()中,这也可以说是更好的OO设计,但当然,您还必须记住

  • 它会让其他人更难理解,因为复制品不再是大多数人所期望的。类似地,处理类的其他代码(包括标准容器)可能会出现错误行为。解决这一问题的一种方法是为类定义一个
    操作符==
    方法(在重写复制构造函数时,即使不使用该方法,这也是一个好主意),以使其在概念上“合理”,并作为一种内部文档。如果您的类得到了大量的使用,那么您可能还会提供一个
    操作符=
    ,这样您就可以保持自动生成的实例id与应该在该操作符下进行的类成员分配的分离。等等;)

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


如果您知道所有这些问题,并且相信您可以处理这些后果(当然这在很大程度上取决于您的特定上下文),那么重写复制构造函数是一个可行的选择。毕竟,语言特征的存在是有原因的。显然,它不像看上去的那么简单,你应该小心。

< p>向量是使用C++生成的复制构造函数,而不用问。一个“C”是实例化的,其余的是从原型中复制的。

@James:假设我必须能够区分每个对象,即使多个对象(暂时)具有相同的值。由于vector的重新分配,我对它的地址不太信任。此外,不同的对象可以在不同的容器中。您提到的问题是否与以下约定有关,或者此类代码是否存在真正的技术问题?我做的测试效果很好。
这就是我的意思:

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

struct C {
    int id;
    static int n;
    int data;

    C() {               // not called from vector
        id = n++;
        data = 123;
    }

    C(const C& other) {
        id = n++;
        data = other.data;
    }

    bool operator== (const C& other) const {
        if(data == other.data)      // ignore id
            return true;
        return false;
    }
};

int C::n = 0;


int main()
{
    vector<C> vc;
    deque<C> dc;

    vc.resize(10);

    dc.resize(8);

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

    for(int i = 0; i < vc.size(); ++i)
        cout << "[vector] " << i << ": " << vc[i].id << ";  data = " << vc[i].data << endl;

    for(int i = 0; i < dc.size(); ++i)
        cout << "[deque] " << i << ": " << dc[i].id << ";  data = " << dc[i].data << endl;
}

这不行。复制构造函数需要复制;如果没有,则该类不能在标准容器中使用。GMan:假设这个类有一些需要复制的数据,以及一个必须是唯一的成员变量(id)。在这种情况下,编写一个复制构造函数来复制它所能复制的内容,并为id分配一个新的、唯一的值,这难道不正确吗?如果这是一种不好的继续方式,是否有其他选择?@sje:不,它不能在stan中使用
#include <iostream>
#include <vector>
#include <deque>
using namespace std;

struct C {
    int id;
    static int n;
    int data;

    C() {               // not called from vector
        id = n++;
        data = 123;
    }

    C(const C& other) {
        id = n++;
        data = other.data;
    }

    bool operator== (const C& other) const {
        if(data == other.data)      // ignore id
            return true;
        return false;
    }
};

int C::n = 0;


int main()
{
    vector<C> vc;
    deque<C> dc;

    vc.resize(10);

    dc.resize(8);

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

    for(int i = 0; i < vc.size(); ++i)
        cout << "[vector] " << i << ": " << vc[i].id << ";  data = " << vc[i].data << endl;

    for(int i = 0; i < dc.size(); ++i)
        cout << "[deque] " << i << ": " << dc[i].id << ";  data = " << dc[i].data << endl;
}
C::n = 20
[vector] 0: 1;  data = 123
[vector] 1: 2;  data = 123
[vector] 2: 3;  data = 123
[vector] 3: 4;  data = 123
[vector] 4: 5;  data = 123
[vector] 5: 6;  data = 123
[vector] 6: 7;  data = 123
[vector] 7: 8;  data = 123
[vector] 8: 9;  data = 123
[vector] 9: 10;  data = 123
[deque] 0: 12;  data = 123
[deque] 1: 13;  data = 123
[deque] 2: 14;  data = 123
[deque] 3: 15;  data = 123
[deque] 4: 16;  data = 123
[deque] 5: 17;  data = 123
[deque] 6: 18;  data = 123
[deque] 7: 19;  data = 123