Нужно ли уничтожать объект, когда по его адресу назначается новый объект? - PullRequest
2 голосов
/ 09 июля 2020

Рассмотрим следующий код.

#include <stdio.h>
using namespace std;

constexpr size_t TOTAL = 2;

class thing
{
private:
    inline static size_t NEXT_ID = 0;
    size_t id;

public:
    thing() : id(NEXT_ID++)
    {
        printf("Thing %zd created.\n", this->id);
    }
    ~thing()
    {
        printf("Thing %zd destroyed.\n", this->id);
    }
};

class container
{
private:
    inline static size_t NEXT_ID = 0;
    size_t id;
    thing* things;

public:
    container() : id(NEXT_ID++)
    {
        this->things = new thing[TOTAL];
        printf("Container %zd created.\n", this->id);
    }
    ~container()
    {
        delete[] this->things;
        printf("Container %zd destroyed.\n", this->id);
    }

    thing& at(size_t idx)    // this is the important method
    {
        return this->things[idx];
    }
};

int main()
{
    container c;
    c.at(0) = thing();   // here is the problem
    return 0;
}

Результат - то, чего я не ожидал.

Thing 0 created.
Thing 1 created.
Container 0 created.
Thing 2 created.
Thing 2 destroyed.
Thing 1 destroyed.
Thing 2 destroyed.
Container 0 destroyed.

Я знаю, что Thing 2 был временным объектом, поэтому он был уничтожен дважды. У меня есть несколько вопросов о том, что случилось с Thing 0.

  • Почему Thing 0 не был уничтожен?
  • Произойдет ли утечка памяти?
  • Нужно ли мне как-то уничтожать Thing 0 или он был успешно перезаписан?

Ответы [ 2 ]

3 голосов
/ 09 июля 2020

Нет двойного вызова деструктора для одного и того же объекта. Проблема только в вашем выводе. Вы печатаете id, но назначение копии в c.at(0) = thing(); копирует id из временного объекта в объект в контейнере. Это причина того, что вы видите две «Вещи 2 уничтожены». и нет «Вещь 0 уничтожена».

Если вам нужен лучший механизм регистрации, вы можете распечатать указатель this. Адрес объекта не меняется за время существования объекта, это уникальный идентификатор объекта. Конечно, для удобства вы можете дополнительно распечатать id.

printf("Thing %p %zd created.\n", static_cast<void*>(this), this->id);
printf("Thing %p %zd destroyed.\n", static_cast<void*>(this), this->id);

Это должно дать вам примерно такой результат (конечно, 0x11111111, 0x22222222 и 0x33333333 будут выглядеть иначе в вашем случае):

Вещь 0x11111111 0 создана. Вещь 0x22222222 1 создана. Контейнер 0 создан. Вещь 0x33333333 2 создана. Вещь 0x33333333 2 уничтожена. Вещь 0x22222222 1 уничтожена. Вещь 0x11111111 2 уничтожена. Контейнер 0.

1 голос
/ 09 июля 2020

В этом операторе

c.at(0) = thing();

используется неявно определенное оператором присваивания копии компилятора. Таким образом, член данных id объекта, на который ссылается выражение c.at(0), становится равным идентификатору временного объекта, созданного выражением thing(), который равен 2.

В этом утверждении временный объект создается и в конце уничтожается

Thing 2 created.
Thing 2 destroyed.

Теперь объект c содержит два подобъекта thing, сохраненных как элементы массива. Подобъекты имеют идентификаторы 2 и 1.

Они удаляются в порядке, обратном их созданию

Thing 1 destroyed.
Thing 2 destroyed.  // previously it has id equal to 0

Таким образом, программа не имеет утечка памяти. Все созданные объекты были успешно удалены, как видно из вывода программы.

...