c++ 中的矢量元素始终设置为最后一个元素

Vector elements in c++ always set to last element

本文关键字:元素 设置 最后一个 c++      更新时间:2023-10-16

我想将类的成员存储到向量中。在我的类中,我有一些私有变量,我通过常量指针访问它们,以便保护它们不被更改(见这篇文章)。问题:当我在循环期间将类的实例添加到向量中并在之后访问它们时,所有元素似乎是最后一个添加的。这是MWE:

#include <iostream>
#include <vector>
using namespace std;
class myClass {
private:
int x_;
void init(int x) {
x_ = x;
}
public:
const int &x;
myClass(int x) : x(x_) {
init(x);
};
};
int main(int argc, const char * argv[]) {
vector<myClass> myVector;
// Does not work
for (int j=0; j<3; j++) {
myVector.push_back(myClass(j));
}
for (int j=0; j<3; j++) {
cout << "1st attempt: " << myVector.at(j).x << endl;
}
myVector.clear();
// Works
for (int j=0; j<3; j++) {
myVector.push_back(myClass(j));
cout << "2nd attempt: " << myVector.at(j).x << endl;
}
myVector.clear();
// Works also
myVector.push_back(myClass(0));
myVector.push_back(myClass(1));
myVector.push_back(myClass(2));
for (int j=0; j<3; j++) {
cout << "3rd attempt: " << myVector.at(j).x << endl;
}
return 0;
}

问题:我做错了什么,我可以修复它吗?

我做错了什么,我可以修复它吗?

好吧,这个想法总体上看起来是错误的。 撇开这一点:如果你想"修复它",你需要考虑当你push_back时发生了什么:

for (int j=0; j<3; j++) {
myVector.push_back(myClass(j));
}

您按值传递类,因此调用复制构造函数。 因此push_back没有得到myClass(j),而是得到了一份副本。 然而你没有写一个复制构造函数...那么,您的复制构造函数从何而来? 你得到的是编译器默认值,它只是说副本的int &x获得与原始对象相同的值(而不是新对象的x_)。

这意味着向量中副本内部的引用是指向您作为myClass(j)传入的原始对象。 然而,该原始参数是在push_back调用完成后释放的。 每次通过循环时,内存空间可能会被重用,这就是您看到最后一个过时值的原因。 尝试通过 Valgrind 或类似方式运行它,它可能会发现一个问题。

您可以通过编写自己的复制构造函数来解决此问题,该构造函数执行您的实际含义:

class myClass {
private:
int x_;
void init(int x) {
x_ = x;
}
public:
const int &x;
myClass(int x) : x(x_) {
init(x);
};
myClass(myClass const & other) : x_ (other.x_), x(x_) {
}
};

通过这种方式,您可以明确地告诉编译器您将int const &链接到int的隐含愿望。 如果你不告诉它,它根本无法知道它们是相关的。

但是可能有很多原因首先不做这种界面。 只需使用普通的访问器方法,除非您已经想到了这样做的真正充分的理由。

(还要注意@JoachimPileborg对emplace_back的建议,很高兴知道这一点。

扩展我的评论...

当你这样做时

myVector.push_back(myClass(0));

你创建一个临时对象myClass(0),一个对象在push_back函数返回后被销毁。这个临时对象被复制到向量中,我认为副本中x的成员变量将引用临时对象的成员变量x_。当然,当您以后使用x时,这会导致未定义的行为,该现在引用了已破坏对象中的数据。

有两种方法可以解决此问题:

  1. 使用emplace_back而不是push_back
  2. 添加一个复制构造函数,使x引用对象自己的x_变量。

当您将对象添加到vector时,该对象将被复制,并调用该类的复制构造函数。由于myClass具有引用成员,因此默认复制构造函数不适用于所需的语义,因此需要定义自己的语义:

myClass (const myClass& rhs) : x(x_) {
init(rhs.x);
}

当您使用它时,您可能还希望定义一个赋值运算符:

myClass& operator= (const myClass& rhs) {
x_ = rhs.x;
return *this;
}

通过修复myClass的定义很容易修复,因此:

class myClass {
private:
public:
const int x;
myClass(int x_) : x(x_) {
};
};

合理的:

myClass 实现了数据即接口习惯用法,这很好,但如果你要公开数据,请通过公开实际数据来公开数据 - 而不是对它的引用。

请记住,引用实际上是下面的指针,因此它包含一个内存地址,并且它在类中的存在会阻止自动生成移动构造函数。