Деструктор вызывается для объекта при добавлении его в std::list

У меня есть объект Foo и std::list, содержащий его экземпляры. Моя проблема в том, что, когда я добавляю новый экземпляр в список, он сначала вызывает ctor, а затем и dtor. А потом дтор на другом экземпляре (по указателю this).

Отдельный экземпляр добавляется в список, но так как вызывается его dtor (вместе с его родителями), объект не может использоваться, как ожидается.

Вот некоторый упрощенный код для иллюстрации проблемы:

#include <iostream>
#include <list>

class Foo
{
public:
    Foo()
    { 
        int breakpoint = 0;
    }
    ~Foo()
    { 
        int breakpoint = 0;
    }
};

int main()
{
    std::list<Foo> li;
    li.push_back(Foo());
}

5 ответов

Решение

Когда вы push_back() вашего объекта Foo, объект копируется во внутренние структуры данных списка, поэтому вызываются Dtor и Ctor другого экземпляра.

Все стандартные типы контейнеров STL в C++ принимают свои элементы по значению, поэтому копируют их по мере необходимости. Например, всякий раз, когда необходимо увеличить вектор, возможно, что все значения в векторе будут скопированы.

Может быть, вы хотите хранить указатели вместо объектов в списке. При этом копируются только указатели вместо объекта. Но при этом вы должны обязательно удалить объекты, как только закончите:

for (std::list<Foo*>::iterator it = list.begin(); it != list.end(); ++it) {
    delete *it;
}
list.clear();

В качестве альтернативы вы можете попробовать использовать какой-нибудь класс "умного указателя", например, из библиотек Boost.

Вы создаете временный Foo здесь:

li.push_back( Foo() )

push_back копирует этот Foo во внутренние структуры данных. Временный Foo уничтожается после выполнения push_back, который вызовет деструктор.

Вам понадобится правильный конструктор копирования, который увеличит количество ссылок на членов класса, которые вы не хотите уничтожать на ранних этапах, или сделаете его закрытым, чтобы заставить себя использовать указатель.

Используйте этот объект, чтобы понять:

class Foo
{
public:
    Foo(int x): m_x(x)
    { 
    std::cout << "Constructed Object: " << m_x << ")\n";
    }
    Foo(Foo const& c): m_x(c.m_x+100)
    {
    std::cout << "Copied Object: " << m_x << ")\n";
    }
    ~Foo()
    {  
    std::cout << "Destroyed Object: " << m_x << ")\n";
    }
};

Первый главный

std::list<Foo*> li;
li.push_back(Foo(1));

Здесь мы создаем временный объект Foo и вызываем push_back(). Временный объект копируется в список, а функция возвращается. По завершении этого оператора временный объект затем уничтожается (через деструктор). Когда список уничтожается, он также уничтожает все содержащиеся в нем объекты (Foo - объект с деструктором, поэтому уничтожение включает вызов деструктора).

Так что вы должны увидеть что-то вроде этого:

Constructed Object: 1
Constructed Object: 101
DestroyedObject: 1
DestroyedObject: 101

Во втором примере у вас есть:

std::list<Foo*> li;
li.push_back(new Foo(1));

Здесь вы динамически создаете объект в куче. Затем вызовите push_back(). Здесь указатель копируется в список (указатель не имеет конструктора / деструктора), поэтому больше ничего не происходит. Список теперь содержит указатель на объект в куче. Когда функция возвращает, больше ничего не делается. Когда список уничтожается, он уничтожает (обратите внимание на тонкую разницу между уничтожением и удалением) объект, который он содержит (указатель), но указатель не имеет деструктора, поэтому ничего не произойдет, вы потеряете память.

Так что вы должны увидеть что-то вроде этого:

Constructed Object: 1

На самом деле происходит то, что вы сохраняете копию переданного объекта в списке, потому что вы отправляете ее по значению, а не по ссылке. Таким образом, первый вызываемый dtor фактически вызывается для объекта, который вы передаете методу push_back, но к тому времени был создан новый экземпляр, который теперь сохраняется в списке.

Если вы не хотите, чтобы была создана копия объекта Foo, сохраните указатели на объекты Foo в списке вместо самих объектов. Конечно, при этом вам нужно будет правильно освободить память при уничтожении списка.

Создание списка, содержащего указатели вместо экземпляров, решает проблему с вызываемым деструктором. Но я все еще хочу понять, почему это происходит.

#include <iostream>
#include <list>

class Foo
{
public:
    Foo()
    { 
        int breakpoint = 0;
    }
    ~Foo()
    { 
        int breakpoint = 0;
    }
};

int main()
{
    std::list<Foo*> li;
    li.push_back(new Foo());
}
Другие вопросы по тегам