Здравствуйте, Muxa, Вы писали:
M>Представьте себе иерархию классов.
M>Разработчик самого базового из них не сделал деструктор виртуальным.
M>Для чего он мог это сделать?
Ещё как вариант (кроме только стекового использования) заставить пользователя пользоваться правильными смартпоинтерами — boost::shared_ptr, а ему виртуальный деструктор не нужен.
[In theory there is no difference between theory and practice. In
practice there is.]
[Даю очевидные ответы на риторические вопросы]
Здравствуйте, rg45, Вы писали:
R>Здравствуйте, Muxa, Вы писали:
M>>Представьте себе иерархию классов.
M>>Разработчик самого базового из них не сделал деструктор виртуальным.
M>>Для чего он мог это сделать?
R>Реализовать гибкую стратегию владения объектами можно и без виртуальных деструкторов. Приведу пример. Я определяю некий абстракный интерфейс, состоящий полностью из чисто виртуальных функций:
Похожая стратегия используется в COM. Более того, при объявлении интрерфейсов (обычно они генерируются из IDL), виртуальный деструктор в интерфейсе не просто не нужен, а даже запрещен. Так как объявляя его вносится смещение в табицу виртуальных функций и интерфейс становится не совместимым c COM.
class IUnknown
{
public:
virtual int AddRef();
virtual int Release();
virtual HRESULT QueryInterface(...параметры...);
};
class IA: public IUnknown
{
public:
virtual HRESULT A();
};
При таком описании интерфейсов удобно использовать шаблон имплементирующий AddRef, Reelase для любого класса без всяких виртуальных деструкторов:
template<class Implementation>
class ComClass: Implementation
{
public:
public int AddRef() override
{
++counter;
return counter;
}
public int Release() override
{
--counter;
if(counter == 0)
{
delete this;
return 0;
}
}
static intrusive_ptr<Implementation> new_()
{
return intrusive_ptr<Implementation>(new ComClass());
}
private:
int counter;
ComClass() {}
ComClass(ComClass const &) {}
};
Пример:
// Абстрактный класс, AddRef, Release не реализованны (зачем их каждый раз реализовывать?).
// Но реализованны функции интерфейса A.
class AImpl: IA
{
public:
HRESULT A() override { ... }
};
ptr<AImpl> x = ComClass<AImpl>::new_();
x->A();
Здравствуйте, bzzz, Вы писали:
R>>...
B>Всё классно но ваш пример не работает, не могли бы вы написать работающий пример?
Да, конечно, приведенные мной фрагменты кода носят чисто описательный характер и не претендуют на статус работающего примера.
А вот работающий пример.
//IFoo.hpp
class IFoo
{
public:
virtual void bar() = 0;
protected:
IFoo() { }
};
//*.cpp
//#include "IFoo.hpp"
#include <iostream>
#include <boost/shared_ptr.hpp>
#include <vector>
using boost::shared_ptr;
#define PRINT_FUNCTION_NAME() { std::cout << __FUNCTION__ << std::endl; }
class FooDerived1 : public IFoo
{
public:
virtual void bar() { PRINT_FUNCTION_NAME(); }
static shared_ptr<FooDerived1> create_instance()
{
return shared_ptr<FooDerived1>(new FooDerived1(), destroy);
}
protected:
~FooDerived1() { PRINT_FUNCTION_NAME(); }
static void destroy(FooDerived1* inst) { delete inst; }
};
class FooDerived2 : public IFoo
{
public:
virtual void bar() { PRINT_FUNCTION_NAME(); }
virtual ~FooDerived2() { PRINT_FUNCTION_NAME(); }
};
void use(const std::vector<shared_ptr<IFoo> >& items)
{
typedef std::vector<shared_ptr<IFoo> > Items;
for(Items::const_iterator item = items.begin(); item != items.end(); ++item)
(*item)->bar();
}
void do_nothing(void*) { }
int main()
{
typedef std::vector<boost::shared_ptr<IFoo> > Items;
Items items;
FooDerived2 local;
items.push_back(shared_ptr<FooDerived2>(&local, do_nothing));
items.push_back(FooDerived1::create_instance());
items.push_back(shared_ptr<FooDerived2>(new FooDerived2()));
use(items);
}
Output:
FooDerived2::bar
FooDerived1::bar
FooDerived2::bar
FooDerived2::~FooDerived2
FooDerived1::~FooDerived1
FooDerived2::~FooDerived2
Собрано на msvc-9.0 (MSVS 2008), boost 1.45.0.
А теперь немного повторюсь. Ценность этого подхода в том, мухи отделены от котлет. Те части программы, которые используют полиморфные объекты, не интересуются ни способом их создания, ни областью памяти, в которой хранятся эти объекты, ни временем их жизни. Так, в этом примере два объекта расположены в динамической памяти, а один — во временной памяти функции
main. Но внутри функции
use, использующей эти объекты, все эти детали не интересны. А защищенный деструктор в абстрактном базовом классе дает нам защищенность от очумелых ручек, способных написать что угодно, в т.ч. и такое:
shared_ptr<IFoo> shared = ...;
IFoo* ptr = shared.get();
//...
delete ptr;
//...