#include <iostream>
#include <vector>
#include <cstdlib>
#include <cassert>
struct s_A {
bool bin;
s_A(): bin(0) {}
};
class c_A {
public:
s_A * p_struct;
c_A(): p_struct(NULL) {p_struct = new s_A [16];}
void Reset()
{
delete [] p_struct;
p_struct = new s_A [16];
}
};
int main ()
{
srand(1);
int x = 30;
std::vector <c_A> objects;
objects.assign(x, c_A());
std::vector <c_A> objects_copy;
for(int q=0; q < x; q++)
{
objects_copy.push_back(objects[ rand() % x ]);
objects_copy[q].Reset();
}
for(int q=0; q < 16; q++)
for(int w=0; w < x; w++)
{
// Assertion should not fail, but it does
assert(!objects_copy[w].p_struct[q].bin);
objects_copy[w].p_struct[q].bin = true;
}
}
不知何故,不同复制对象中的指针最终指向同一个内存,并且断言最终会失败。如果在未复制的vector上运行,则不会发生这种情况。我认为c_A.Reset()应该释放指针(通过delete [])指向一个新数组,但我显然遗漏了一些东西。
答案 0 :(得分:5)
问题的具体来源是这些行:
objects_copy.push_back(objects[ rand() % x ]);
objects_copy[q].Reset();
问题在于,当您尝试将对象的副本推送到objects_copy
时,最终会在objects
vector
中制作对象的浅表副本。这意味着两个向量中的对象最终将具有彼此复制的指针。因此,当您对Reset
objects_copy
的元素调用vector
时,将释放仍由objects
数组的元素指向的内存。
问题是您的c_A
班级违反了rule of three。因为您的类封装了资源,所以它需要具有析构函数,复制构造函数和复制赋值运算符。如果您定义这三个函数,那么当您尝试将对象复制到objects_copy
vector
时,您将能够管理底层资源,可能是通过复制或通过引用计数。有关如何编写这些函数的详细信息,请查看this description如何编写这些函数。
编辑:以下是对正在发生的事情的详细说明:
问题在于,当您向vector
添加对象时,实际上并未将该对象存储在vector
中。相反,您正在存储该对象的副本。因此,当您编写objects_copy.push_back(objects[ rand() % x ]);
时,您不会在vectors
中存储相同的对象。相反,您正在创建objects
中某个对象的副本,并将其存储在objects_copy
中。由于您的c_A
类型没有定义复制函数,因此最终会生成对象的浅表副本,从而创建指针的副本。这意味着如果您考虑objects
列表中的原始对象及其objects_copy
中的相应副本,它们将分别具有相同p_struct
指针的副本。当您在Reset
objects_copy
中的对象上调用vector
时,可以释放其指针指向的内存。但是,您没有更新objects
中存储的原始对象的指针,因此指针现在指向垃圾内存。尝试使用该指针会导致未定义的行为,从而导致崩溃。
添加复制功能将通过允许您控制复制的方式来解决此问题。如果为c_A
定义复制函数,导致副本指向原始对象指向的对象的新副本,则不会发生此问题,因为每个对象都有自己的单独指针。或者,如果您使用引用计数,那么如果您知道某个其他对象指向该资源,则可以通过不删除该资源来避免该问题。
希望这有帮助!