C++, "соединение" указателей-членов в нескольких объектах, скопированных из одного и того же исходного объекта
#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;
}
}
Каким-то образом указатели в различных скопированных объектах в конечном итоге указывают на одну и ту же память, и утверждение в конечном итоге не выполняется. Такое поведение не происходит, если выполняется на не скопированном векторе. Я думал, что c_A.Reset() должен освободить указатели (через delete[]) для указания на новый массив, но я, очевидно, что-то упускаю.
1 ответ
Конкретным источником вашей проблемы являются следующие строки:
objects_copy.push_back(objects[ rand() % x ]);
objects_copy[q].Reset();
Проблема заключается в том, что при попытке вставить копии объектов в objects_copy
в конечном итоге вы делаете мелкую копию объектов в objects
vector
, Это означает, что объекты в двух векторах будут иметь указатели, которые являются копиями друг друга. Следовательно, когда вы вызываете Reset
на элементах objects_copy
vector
Вы освобождаете память, на которую все еще указывают элементы objects
массив.
Проблема в том, что ваш c_A
класс нарушает правило трех. Поскольку ваш класс инкапсулирует ресурс, он должен иметь деструктор, конструктор копирования и оператор присваивания копии. Если вы определите эти три функции, то при попытке скопировать объекты в objects_copy
vector
у вас будет возможность управлять базовым ресурсом, возможно, путем дублирования или подсчета ссылок. Для получения подробной информации о том, как написать эти функции, посмотрите это описание того, как написать эти функции.
РЕДАКТИРОВАТЬ: Вот более подробное описание того, что происходит:
Проблема в том, что когда вы добавляете объект в 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
это заставляет копию указывать на новую копию объекта, на который указывает оригинал, тогда эта проблема не будет возникать, потому что у каждого объекта будет свой собственный отдельный указатель. В качестве альтернативы, если вы используете подсчет ссылок, вы можете избежать проблемы, не удаляя ресурс, если знаете, что на него указывает какой-то другой объект.
Надеюсь это поможет!