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

发布于 2024-09-10 11:08:28 字数 820 浏览 9 评论 0原文

当我创建对象的 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 = 1
0: 0
1: 0
2: 0
...

这就是我想要的:

C::n = 10
0: 0
1: 1
2: 2
...

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

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

谢谢!

When I create a std::vector of objects, the constructor of these objects is not always called.

#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;  
}

This is the output I get:

C::n = 1
0: 0
1: 0
2: 0
...

This is what I would like:

C::n = 10
0: 0
1: 1
2: 2
...

In this example, am I forced to resize the vector and then initialise its elements "manually"?
Could the reason be that the elements of a vector are not initialised in an ordered way, from the first to the last, and so I cannot obtain a deterministic behaviour?

What I would like to do, is to easily count the number of objects created in a program, in different containers, in different points of the code, and to give a single id to each of them.

Thank's!

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

深海蓝天 2024-09-17 11:08:28

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

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

void resize(size_type sz, T c = T());

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

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

[有兴趣的请注意:在C++03中,resize() (c)的第二个参数是按值获取的;在 C++0x 中,它由 const 左值引用获取(请参阅 LWG 缺陷 679)]。

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

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

std::vector<C> vc;
for (unsigned i(0); i < 10; ++i)
    vc.push_back(C());

the constructor of these objects is not always called.

Yes, it is, but it's not the constructor you think. The member function resize() is actually declared like this:

void resize(size_type sz, T c = T());

The second parameter is the object to copy into each of the newly inserted elements of the vector. If you omit the second parameter, it default constructs an object of type T then copies that object into each of the new elements.

In your code, a temporary C is constructed and the default constructor is called; id is set to 0. The implicitly declared copy constructor is then called ten times (to insert ten elements into the vector), and all of the elements in the vector have the same id.

[Note for those who are interested: in C++03, the second parameter of resize() (c) is taken by value; in C++0x it is taken by const lvalue reference (see LWG Defect 679)].

In this example, am I forced to resize the vector and then initialise its elements "manually"?

You can (and probably should) insert the elements into the vector individually, e.g.,

std::vector<C> vc;
for (unsigned i(0); i < 10; ++i)
    vc.push_back(C());
咆哮 2024-09-17 11:08:28

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

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

struct C {
//....
C(const C& other) {
    id = n++;
    // copy other data members
}
//....
};

由于 vector::resize 的工作方式(它有第二个可选参数,用作它创建的副本的“原型”,具有默认值在您的 C() 示例中),这会在您的示例中创建 11 个对象(“原型”及其 10 个副本)。

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

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

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

  • 这可能会让其他人更难理解,因为副本不再是大多数人所期望的。同样,处理类(包括标准容器)的其他代码可能会出现错误。解决这个问题的一种方法是为您的类定义一个 operator== 方法(并且它可能会争论,即使您不使用该方法,覆盖复制构造函数时这是一个好主意),使其在概念上保持“健全”,并作为一种内部文档。如果您的类得到大量使用,您可能最终还会提供一个 operator= ,以便您可以将自动生成的实例 id 与应在该运算符下进行的类成员分配保持分离。等等 ;)

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

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

The reason is that vector::resize inserts copies by calling the automatically provided copy constructor, rather than the constructors you have defined in your example.

In order to get the output you want, you can define the copy constructor explicitly:

struct C {
//....
C(const C& other) {
    id = n++;
    // copy other data members
}
//....
};

Because of the way vector::resize works though (it has a second optional argument used as a 'prototype' for the copies it creates, with a default value in your case of C()), this creates 11 objects in your example (the 'prototype' and 10 copies of it).

Edit (to include some of the good advice in the many comments):

There are several downsides to this solution worth noting, as well as some options and variants that are likely to yield more maintainable and sensible code.

  • This method does add maintenance costs, and an amount of risk. You have to remember to modify your copy constructor whenever you add or remove members variables of the class. You don't have to do that if you rely on the default copy constructor. One way to combat this problem is to encapsulate the counter in another class (like this), which is also arguably better OO design, but then of course you also have to keep in mind the many issues that can crop up with multiple inheritance.

  • It can make it harder for other people to understand, because a copy is no longer exactly what most people would expect. Similarly, other code that deals with your classes (including the standard containers) may misbehave. One way to combat this is to define an operator== method for your class (and it may be argued that this is a good idea when overriding the copy constructor even if you don't use the method), to keep it conceptually 'sound' and also as a kind of internal documentation. If your class gets much use, you will likely also end up providing an operator= so that you can maintain the separation of your automatically generated instance id from class member assignments that should take place under this operator. And so on ;)

  • It might disambiguate the whole issue of 'different id values for copies' if you have enough control over the program to use dynamically created instances (via new) and use pointers to those inside containers. This does mean you need to 'initialise elements "manually"' to some degree - but it's not a lot of work to write a function that gives you back a vector full of pointers to new, initialised instances. If you consistently deal with pointers when using standard containers, you won't have to worry about the standard containers creating any instances 'under the covers'.

If you're aware of all those issues, and believe you can cope with the consequences (which is of course highly dependent on your particular context), then overriding the copy constructor is a viable option. After all, the language feature is there for a reason. Obviously, it is not as simple as it looks, and you should be careful.

腻橙味 2024-09-17 11:08:28

该向量使用 C++ 为您生成的复制构造函数,无需询问。实例化一个“C”,其余部分从原型复制。

The vector is using the copy constructor the c++ generates for you without asking. One "C" is instantiated, the rest is copied from the prototype.

流云如水 2024-09-17 11:08:28

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

#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

@James: Let's say that I have to be able to distinguish every object, even if more than one can (temporarily) have the same value. Its address is not something I would trust so much, due to vector's reallocations. Furthermore, different objects can be in different containers. Are the problems you mention just related to the followed conventions, or can be there real technical problems with such code? The test I did works well.
This is what I mean:

#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;
}

Output:

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
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文