18 ноября 2008

The magic of shared_ptr

В качестве еще одной иллюстрации к предыдущей заметке про "магию shared_ptr", в ответ один из комментариев - небольшой пример: создадим простую иерархию классов, где базовый класс не имеет виртуального деструктора.
struct A { ~A() { cout << "~A()" << endl; } };
struct B : public A { ~B() { cout << "~B()" << endl; } };
А теперь создадим объект класса B и поместим его в контейнер указателей на A:
{
auto_ptr<B> b(new B);
ptr_vector<A> x;
x.push_back(b);
}

{
auto_ptr<B> b(new B);
vector< shared_ptr<A> > x;
x.push_back(b);
}
В случае контейнера, хранящего только указатели (в данном случае - boost::ptr_vector) - получим при разрушении объекта вызов только ~A(), а в случае с shared_ptr будет вызван ~B(), так как shared_ptr немного умнее обычного указателя.

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

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

Ну ты же знаешь, что за такое надо отрывать уши. Как (!!!) можно полиморфно использовать указатель на предка при таком наследовании?

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

Ты не поверишь, но не всегда нужен полиморфизм.

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

Ох-хо-хо.. Я вовсе не пытаюсь тебе оскорбить, но дело в том, что С++ мстит очень жестко. В данном случае ты привел неудачный пример использования boost::shared_ptr.
Почитай "Стандарты кодирования" Александреску и Саттера. Если класс не имеет виртуального деструктора - он НЕ предназначен для наследования. Если ты все же хочешь воспользоваться функциональностью такого класса - инкапсулируй его. Ты считаешь в примере нет "граблей"?

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

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

По теме. Наглядный пример: наследники ATL::CWindow. Если поместить их в контейнер как shared_ptr<ATL::CWindow>, то для кода, который будет использовать этот контейнер они - лишь обертка над HWND. Полиморфизм у них реализован через WindowProc() и отлично работает без виртуальных методов. У некоторых объектов будут и виртуальные методы в понимании C++ (у наследников от CWindowImpl<..., CWindow, ...>), у других (CEdit) - нет. И "снаружи" не стоит об этом задумываться: CWindow сработает как обертка HWND, деструктор shared_ptr вызовет нужный деструктор для каждого конкретного объекта. Где грабли?

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

А как тогда можно воспользоваться вектором x? Как ты определяешь объект какого типа содержится в i-м элементе? Или ты используешь vector< shared_ptr < > > только для удаления их в некоторый момент? А имеет ли вообще смысл хранить контролы в массиве? Своеобразный сборщик мусора?

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

Пользоваться довольно просто:
BOOST_FOREACH(boost::shared_ptr<CWindow> &Window, Windows)
{
    Window->SetWindowText(...);
    Window->ShowWindow(...);
    Window->...;
}