Re: Зачем нужен невиртуальный деструктор?
От: Vain Россия google.ru
Дата: 10.02.11 18:24
Оценка:
Здравствуйте, Muxa, Вы писали:

M>Представьте себе иерархию классов.

M>Разработчик самого базового из них не сделал деструктор виртуальным.
M>Для чего он мог это сделать?
Ещё как вариант (кроме только стекового использования) заставить пользователя пользоваться правильными смартпоинтерами — boost::shared_ptr, а ему виртуальный деструктор не нужен.
[In theory there is no difference between theory and practice. In
practice there is.]
[Даю очевидные ответы на риторические вопросы]
Re[2]: Зачем нужен невиртуальный деструктор?
От: sergey_shandar США http://getboost.codeplex.com/
Дата: 11.02.11 11:19
Оценка: 4 (1)
Здравствуйте, 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();
getboost.codeplex.com
citylizard.codeplex.com
com
Re[3]: Зачем нужен невиртуальный деструктор?
От: rg45 СССР  
Дата: 14.02.11 04:40
Оценка: 1 (1)
Здравствуйте, 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;
//...
--
Справедливость выше закона. А человечность выше справедливости.
Re[4]: Зачем нужен невиртуальный деструктор?
От: bzzz  
Дата: 14.02.11 16:15
Оценка:
Большое спасибо за разъяснение)
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.