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

У меня есть объект Foo и std::list, содержащий его экземпляры. Моя проблема в том, что когда я добавляю новый экземпляр в список, он сначала вызывает ctor, а затем и dtor. А затем 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());
}

person Mizipzor    schedule 05.02.2009    source источник


Ответы (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.

person Ferdinand Beyer    schedule 05.02.2009
comment
Ах! Это многое объясняет, вот почему экземпляры не работают, поскольку у них нет определенного конструктора копирования? Похоже, что указатели - мой лучший выбор. - person Mizipzor; 05.02.2009
comment
mizipzor: При написании класса вы должны решить, должен ли он иметь copy ctor и operator=. Если нет, вы должны объявить частные прототипы для них. Затем вы получите сообщение об ошибке, если ваш код попытается использовать copy ctor или operator=. - person Binary Worrier; 05.02.2009
comment
Библиотека контейнера указателя Boost (boost.org/doc/ libs/1_37_0/libs/ptr_container/doc/) стоит упомянуть здесь. Например, boost::ptr_vector‹Foo› более эффективен и обеспечивает более приятный интерфейс, чем эквивалентный std::vector‹boost::shared_ptr‹Foo› › - person Éric Malenfant; 05.02.2009

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

li.push_back( Foo() )

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

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

person Timbo    schedule 05.02.2009

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

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
person Martin York    schedule 05.02.2009

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

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

person Stas    schedule 05.02.2009

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

#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());
}
person Mizipzor    schedule 05.02.2009
comment
потому что вы никогда не удаляете объект, деструктор на самом деле никогда не вызывается, и у вас есть утечка памяти - person Francois Zard; 11.10.2018