我想将类的成员存储到向量中。在我的课堂上,我有一些私有变量,我通过常量指针访问它们,以防止它们被更改(see this post)。问题:当我在循环期间将类的实例添加到向量中并随后访问它们时,所有元素似乎都是最后添加的元素。这是一个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;
}
显而易见的问题:我做错了什么,能解决吗?
答案 0 :(得分:3)
我做错了什么,能解决吗?
这个想法总的来说是错误的。把它放在一边:如果你想“修复它”,你需要考虑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的建议,这是很好的了解。)
答案 1 :(得分:2)
扩展我的评论......
当你这样做时
myVector.push_back(myClass(0));
使用myClass(0)
创建临时对象,该对象在push_back
函数返回后被破坏。这个临时对象被复制到向量中,我认为副本中的成员变量x
将引用临时对象的成员变量x_
。当你稍后使用现在引用被破坏对象中的数据的x
时,这当然会导致未定义的行为。
有两种方法可以解决这个问题:
emplace_back
代替push_back
。x
引用对象所拥有的x_
变量。答案 2 :(得分:1)
当您将对象添加到vector
时,它会被复制,调用该类&#39;复制构造函数。因为myClass
有一个引用成员,所以默认的复制构造函数不适用于你想要的语义,所以你需要定义自己的语义:
myClass (const myClass& rhs) : x(x_) {
init(rhs.x);
}
当你正在做它时,你可能也想要定义一个赋值运算符:
myClass& operator= (const myClass& rhs) {
x_ = rhs.x;
return *this;
}
答案 3 :(得分:0)
通过修复myClass的定义可以很容易地修复它,因此:
class myClass {
private:
public:
const int x;
myClass(int x_) : x(x_) {
};
};
理性:
myClass实现了数据接口的习惯用法,但是如果你打算通过公开实际的数据而不是对它的引用来公开数据。
请记住,引用实际上是一个指针,因此它包含一个内存地址,并且它在你的类中的存在会阻止自动生成一个移动构造函数。