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в конечном итоге вы делаете мелкую копию объектов в objectsvector, Это означает, что объекты в двух векторах будут иметь указатели, которые являются копиями друг друга. Следовательно, когда вы вызываете Reset на элементах objects_copyvectorВы освобождаете память, на которую все еще указывают элементы objects массив.

Проблема в том, что ваш c_A класс нарушает правило трех. Поскольку ваш класс инкапсулирует ресурс, он должен иметь деструктор, конструктор копирования и оператор присваивания копии. Если вы определите эти три функции, то при попытке скопировать объекты в objects_copyvectorу вас будет возможность управлять базовым ресурсом, возможно, путем дублирования или подсчета ссылок. Для получения подробной информации о том, как написать эти функции, посмотрите это описание того, как написать эти функции.

РЕДАКТИРОВАТЬ: Вот более подробное описание того, что происходит:

Проблема в том, что когда вы добавляете объект в vectorвы на самом деле не храните этот объект в vector, Скорее, вы храните копию этого объекта. Таким образом, когда вы пишете objects_copy.push_back(objects[ rand() % x ]);, вы не храните один и тот же объект в обоих vectors, Вместо этого вы создаете копию одного из объектов из objects и хранить его в objects_copy, Так как ваш c_A Для типа не определены функции копирования, в результате создается мелкая копия объекта, которая создает копию указателя. Это означает, что если вы думаете об оригинальном объекте из objects список и его соответствующая копия в objects_copyкаждый из них будет иметь копию того же p_struct указатель. Когда вы звоните Reset на объекте в objects_copyvectorВы освобождаете память, на которую указывает указатель. Однако вы не обновили указатель исходного объекта, хранящегося в objectsи поэтому этот указатель теперь относится к мусорной памяти. Попытка использовать этот указатель приводит к неопределенному поведению, что приводит к сбою.

Добавление функций копирования исправит это, позволив вам контролировать процесс копирования. Если вы определяете функцию копирования для c_A это заставляет копию указывать на новую копию объекта, на который указывает оригинал, тогда эта проблема не будет возникать, потому что у каждого объекта будет свой собственный отдельный указатель. В качестве альтернативы, если вы используете подсчет ссылок, вы можете избежать проблемы, не удаляя ресурс, если знаете, что на него указывает какой-то другой объект.

Надеюсь это поможет!

Другие вопросы по тегам