17 ноября 2008

Storing dynamicly created objects in a container

Хранение полиморфных объектов в контейнере - классика жанра:
class A { ... };
class B : public A { ... };
std::vector<A*> BunchOfObjects;
BunchOfObjects.push_back(new B);
Главное - не забыть уничтожить удаляемые из контейнера объекты. И если делать это через оператор delete - еще про виртуальный деструктор у базового класса не забыть бы.

Хотя... другое классическое решение позволяет обойтись и без первого и без второго:
std::vector< boost::shared_ptr<A> > BunchOfObjects;
BunchOfObjects.push_back(boost::shared_ptr<A>(new B));
Наличие виртуального деструктора в таком случае не принципиально - shared_ptr запоминает как нужно удалять объект, по умолчанию это будет оператор delete для нужного типа. Таким образом можно хранить, например, список динамически созданных окон - предков ATL::CWindow. Более того, если от хранимых объектов нужна лишь возможность их в нужный момент удалить, то можно вообще не задумываться от их типе используя boost::shared_ptr<void>.

6 комментариев:

sash_ko комментирует...

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

Кстати, в последнем варианте наверно имелось ввиду BunchOfObjects.push_back(boost::shared_ptr< A >(new B));?

Raider комментирует...

Да, в последнем варианте должно быть именно так, как ты написал. Там explicit конструктор.

A.J. комментирует...

Ну если использовать boost, тогда почему бы и сразу не boost::ptr_container, которая берет всю работу с умными указателями на себя, работает быстрее чем вышеприведенное решение, и кроме того позволяет удобно обращаться к элементам, хранящимся в контейнере?

Raider комментирует...

boost::ptr_container не хранит для каждого объекта его deleter, то есть подойдет только если есть виртуальный деструктор у базового класса. Конечно, если виртуальный деструктор у базового класса есть - то ptr_containers - самый лучший выбор (если не нужно делить ни с кем владение объектами, конечно).

Анонимный комментирует...

Так - BunchOfObjects.push_back(boost::shared_ptr< A >(new B)) лучше не писать.
Привожу дословно из документации shared_ptr:
void f(shared_ptr< int >, int);
int g();
void ok()
{
shared_ptr< int > p(new int(2));
f(p, g());
}
void bad()
{
f(shared_ptr< int >(new int(2)), g());
}

Raider комментирует...

Документация описывает пример, в котором может возникнуть последовательность вычисления операндов: operator new, g() /*can throw*/, shred_ptr конструктор. Цитирую:

Since function arguments are evaluated in unspecified order, it is possible for new int(2) to be evaluated first, g() second, and we may never get to the shared_ptr constructor if g throws an exception.

В случае с push_back() операнд всего один и между operator new и конструктором shared_ptr ничего может кинуть исключение. Разве что push_back имеет второй аргумент со значением по умолчанию, конструктор которого может упасть. Но стандарт не предполагает второго аргумента у std::vector::push_back(). Так что все нормально.