Здравствуйте, Muxa, Вы писали:
M>Представьте себе иерархию классов. M>Разработчик самого базового из них не сделал деструктор виртуальным. M>Для чего он мог это сделать?
Чтоб те, кто будут работать с его иерархией, прочувствовали всю прелесть процесса отладки.
Здравствуйте, Muxa, Вы писали:
M>Представьте себе иерархию классов. M>Разработчик самого базового из них не сделал деструктор виртуальным. M>Для чего он мог это сделать?
Как вариант — не предназначал класс для полиморфного удаления.
Здравствуйте, Muxa, Вы писали:
M>Представьте себе иерархию классов. M>Разработчик самого базового из них не сделал деструктор виртуальным. M>Для чего он мог это сделать?
Возможно, для того, что бы не создавалась таблица виртуальных функций (для увеличения быстродействия).
Но, если изначально предполагалось использовать этот класс в качестве базового, то такое решение очень спорное (на мой взгляд).
SB>Возможно, для того, что бы не создавалась таблица виртуальных функций (для увеличения быстродействия).
неужели увеличение производительности в этом случае будет (может быть) ощутимым?
я имею в виду, что конечно можно привести синтетический пример такого кода, но в реальных условиях, программист вряд ли бы принял решения о реализации такой иерархии. (это было личное мнение)
Здравствуйте, Muxa, Вы писали:
M>Представьте себе иерархию классов. M>Разработчик самого базового из них не сделал деструктор виртуальным. M>Для чего он мог это сделать?
Например, чтобы запретить удаление по указателю на базовый класс.
Это может быть полезно, например, при передаче указателя на базовый класс в качестве callback'a куда-нибудь. Чтобы в этом где-нибудь, могли вызывать методы интерфейса, но не могли удалять объект.
Чтобы выразить это явно, а не превращать увлекательную отладку странных глюков, деструктор нужно объявить как protected.
Здравствуйте, Muxa, Вы писали:
M>Представьте себе иерархию классов. M>Разработчик самого базового из них не сделал деструктор виртуальным. M>Для чего он мог это сделать?
Забыл
Не знал
Класс не предназначен для наследования. В этом случае в VMT нет смысла. В большинстве случаев не очень большой прирост в производительности (минус один jump) и по памяти (одно машинное слово на экземпляр класса), но может профилировщик сказал обратное?
Здравствуйте, Muxa, Вы писали:
SB>>Возможно, для того, что бы не создавалась таблица виртуальных функций (для увеличения быстродействия). M>неужели увеличение производительности в этом случае будет (может быть) ощутимым?
может
Не говоря уже о том, наличие вирт. деструктора автоматически переводит класс в категорию не-POD + non-trivial destructor.
M>я имею в виду, что конечно можно привести синтетический пример такого кода, но в реальных условиях, программист вряд ли бы принял решения о реализации такой иерархии. (это было личное мнение)
хз. Надо смотреть на иерархию. Это С++, а не Java, наследование тут используется по очень разным поводам.
T>Забыл T>Не знал
это сделано сознательно
T>Класс не предназначен для наследования. В этом случае в VMT нет смысла.
Класс предназначен для наследования.
T>В большинстве случаев не очень большой прирост в производительности (минус один jump) и по памяти (одно машинное слово на экземпляр класса), но может профилировщик сказал обратное?
про профилировщик не понял. система еще в разработке, появляются производные классы.
Здравствуйте, Muxa, Вы писали:
SB>>Возможно, для того, что бы не создавалась таблица виртуальных функций (для увеличения быстродействия). M>неужели увеличение производительности в этом случае будет (может быть) ощутимым? M>я имею в виду, что конечно можно привести синтетический пример такого кода, но в реальных условиях, программист вряд ли бы принял решения о реализации такой иерархии. (это было личное мнение)
Это может быть актуально, когда экземпляры класса создаются часто и в больших количествах (типа "умных указателей" или чего то подобного). Но, я так понимаю, здесь другой случай..
Здравствуйте, Muxa, Вы писали:
M>Представьте себе иерархию классов. M>Разработчик самого базового из них не сделал деструктор виртуальным. M>Для чего он мог это сделать?
Для того, чтобы избежать оверхед на виртуальный деструктор там, где не планируется полиморфное удаление.
Пример — boost::noncopyable
Здравствуйте, Muxa, Вы писали:
M>Представьте себе иерархию классов. M>Разработчик самого базового из них не сделал деструктор виртуальным. M>Для чего он мог это сделать?
Где-то читал (сам не проверял!) , что компиляторы умеют уложить экземпляр класса в один регистр (EAX) при возврате из функции по значению. При этом размер экземпляра, естественно, должен быть минимален. Если у экземепляра имеется таблица виртуальных методов, то такой фокус вряд ли пройдет.
Люди! Люди, смотрите, я сошел с ума! Люди! Возлюбите друг друга! (вы чувствуете, какой бред?)
T>В большинстве случаев не очень большой прирост в производительности (минус один jump)
считать в инструкциях не очень осмысленно. Вызов невиртуального деструктора в большинстве случаев инлайнится, то есть никаких переходов, а тело функции оптимизируется непосредственно в контектсе вызова. Виртуальный -- это косвенный вызов со всеми прелестями сброса конвейера и отсутсвия оптимизации в контексте вызова.
Здравствуйте, Muxa, Вы писали:
M>Представьте себе иерархию классов. M>Разработчик самого базового из них не сделал деструктор виртуальным. M>Для чего он мог это сделать?
Реализовать гибкую стратегию владения объектами можно и без виртуальных деструкторов. Приведу пример. Я определяю некий абстракный интерфейс, состоящий полностью из чисто виртуальных функций:
Причем деструктор этого абстрактного класса я намеренно объявляю невиртуальным и защищенным, тем самым как бы подчеркивая, что стратегия владения объектами выбирается разработчиком производных классов. При этом у разработчика производного класса есть две возможности: 1) (традиционный) сделать деструктор производного класса открытым и виртуальным; 2) сделать деструктор невиртуальным и защищенным, а для создания объектов реализовать фабричный метод.
Пример второго варианта:
using boost::shared_ptr;
class FooDerived1 : public IFoo
{
public:
virtual void bar();
virtual void baz();
static shared_ptr<FooDerived1> create_instance(/*possible arguments*/);
{
return shared_ptr<FooDerived1>(new FooDerived(/*possible arguments*/));
}
protected:
FooDerived1();
};
class FooDerived2 : public IFoo { /*...*/ };
class FooDerived3 : public IFoo { /*...*/ };
А вот теперь замечательный момент: умный указатель производного типа, возвращаемый фабричным методом create_instance, легко преобразуется в умный указатель абстрактного типа. Это позволяет владеть объектами и корректно удалять их полиморфно:
Преимущество такого подхода: отделение основной функциональности абстрактных классов от способов владения конкретными объектами. Т.о. в прикладной программе точка создания объектов как бы изолирована от использования этих объектов. Это улучшает структурированность кода, тестируемость и пр. и др.
--
Справедливость выше закона. А человечность выше справедливости.
Здравствуйте, Muxa, Вы писали:
M>про профилировщик не понял. система еще в разработке, появляются производные классы.
Выделенное действие неясно. У вас архитектуру на ходу меняют что ли?
Да и вообще у тебя слишком сложный вопрос. Слишком общий. Опиши иерархию, что ли.
А то я легко могу придумать тебе пример класса, который базовый, но вообще без деструктора
Например так:
template<typename TAllocator> struct CAllocatedOn {
void* operator new( size_t s ) { return TAllocator::Alloc( s ); }
void operator delete( void* p ) { TAllocator::Free( p ); }
};
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
boost::shared_ptr<IFoo> p = items[0];
// Объект items удалён, p - единственный владелец указателя
p->bar();
// А теперь p должен быть уничтожен с удалением объекта класса IFoo
// Какой деструктор будет вызван?
Здравствуйте, andrey_nado, Вы писали:
_>Здравствуйте, rg45, Вы писали:
_>А что будет в следующем случае: _>
_>boost::shared_ptr<IFoo> p = items[0];
_>// Объект items удалён, p - единственный владелец указателя
p->>bar();
_>// А теперь p должен быть уничтожен с удалением объекта класса IFoo
_>// Какой деструктор будет вызван?
_>
Будет вызван деструктор производного класса, я пример приводил для иллюстрации именно этого факта. Объясняется это просто: в момент создания экземпляра boost::shared_ptr в нем запоминается не только указатель на объект, но также и функционал deleter, который обеспечивает корректное удаление адресуемых объектов, даже после преобразований.
Еще один аргумент. Деструктор абстрактного класса IFoo в моем примере объявлен защищенным, это является гарантией того, что извне класса этот деструктор не может быть вызван. Поэтому, если Ваша программа откомпилировалась без ошибок, на этот счет можете не волноваться
--
Справедливость выше закона. А человечность выше справедливости.
Здравствуйте, Muxa, Вы писали:
M>Представьте себе иерархию классов. M>Разработчик самого базового из них не сделал деструктор виртуальным. M>Для чего он мог это сделать?
Чтобы спрашивать это на собеседовании и выявлять джуниоров.
SB>Это может быть актуально, когда экземпляры класса создаются часто и в больших количествах (типа "умных указателей" или чего то подобного). Но, я так понимаю, здесь другой случай..
Помоему при создании класса основные "трудозатраты" это работа менеджера памяти и выделение куска. А косвенный переход через виртуальную таблицу — это брызги для моряков по сравнению с выделением памяти и копированием.
Я программист, я Иван Помидоров, хватить трепатся — наш козырь error.
Здравствуйте, Muxa, Вы писали:
M>>Чтобы спрашивать это на собеседовании и выявлять джуниоров. M>сомневаюсь что задумка создателей языка была именно такая.
задумка тут не причем , я попытался представить как такой вопрос может возникнуть.
M>задумка тут не причем , я попытался представить как такой вопрос может возникнуть.
у человека, который не программировал на плюсах, еще и не такие вопросы могут возникнуть
Здравствуйте, Erop, Вы писали:
E>Здравствуйте, Muxa, Вы писали:
M>>про профилировщик не понял. система еще в разработке, появляются производные классы.
E>Выделенное действие неясно. У вас архитектуру на ходу меняют что ли? E>Да и вообще у тебя слишком сложный вопрос. Слишком общий. Опиши иерархию, что ли. E>А то я легко могу придумать тебе пример класса, который базовый, но вообще без деструктора
E>Например так:
template<typename TAllocator> struct CAllocatedOn {
E> void* operator new( size_t s ) { return TAllocator::Alloc( s ); }
E> void operator delete( void* p ) { TAllocator::Free( p ); }
E>};
Как так, нет деструктора? Так не бывает. Пусть сгенерированный компилятором, но деструктор есть всегда. И в данном случае он тоже есть, его можно явно вызвать:
Здравствуйте, rg45, Вы писали:
E>>А то я легко могу придумать тебе пример класса, который базовый, но вообще без деструктора R>Как так, нет деструктора? Так не бывает. Пусть сгенерированный компилятором, но деструктор есть всегда. И в данном случае он тоже есть, его можно явно вызвать:
Ну да. Прошу прощения, это просто жаргон. "без деструктора" надо читать, как "с тривиальным деструктором" конечно же. Это если точно и грамотно выражаться. Что на форуме для новичков С++ наверное правильно.
Спасибо за замечание.
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Re[5]: Не единым аллокатором жив С++, в отличии от... ;)
Здравствуйте, ankf, Вы писали:
A>Помоему при создании класса основные "трудозатраты" это работа менеджера памяти и выделение куска. А косвенный переход через виртуальную таблицу — это брызги для моряков по сравнению с выделением памяти и копированием.
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Здравствуйте, rg45, Вы писали:
R>То, что у всех типов, даже у скалярных, формально есть деструктор можно показать так: R>
На самом деле это не так. Просто доля параметров шаблонов и псевдонимов имён есть специальный синтаксис, похожий на вызовы конструкторов и деструкторов.
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Здравствуйте, Erop, Вы писали:
E>На самом деле это не так. Просто доля параметров шаблонов и псевдонимов имён есть специальный синтаксис, похожий на вызовы конструкторов и деструкторов.
Ну, что касается шаблонов, это я еще понимаю. Но в связи с чем к псевдонимам типов применяются правила, отличные от правил, применяемым к оригинальным именам? Чем обосновано, и где в стандарте то место, по которому следует что так можно:
typedef int Foo;
int i = 42;
i.~Foo();
а так нельзя:
int i = 42;
i.~int();
--
Справедливость выше закона. А человечность выше справедливости.
Здравствуйте, rg45, Вы писали:
R>по которому следует что так можно: R>
R>typedef int Foo;
R>int i = 42;
R>i.~Foo();
R>
Ну я ссылку тебе не дам, но там есть ровно про этот случай и про аргумент шаблона тоже. Что типа даже для строенного типа так писать можно, и это будет обозначать "ничего не делай"
R>а так нельзя: R>
R>int i = 42;
R>i.~int();
R>
R>
А про нельзя нет. Просто такой конструкции нет в стандарте.
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Здравствуйте, Muxa, Вы писали:
M>Представьте себе иерархию классов. M>Разработчик самого базового из них не сделал деструктор виртуальным. M>Для чего он мог это сделать?
Может быть, предполагается совместимость с С? Если нет виртуальных функций, то раскладка полей в памяти будет как в С. Если есть виртуальные функции — то "внутри" объекта появятся указатели на таблицы виртуальных функций (у наследников таких указателей может быть больше, чем 1)
Здравствуйте, npak, Вы писали:
M>>Представьте себе иерархию классов.
N>Может быть, предполагается совместимость с С? Если нет виртуальных функций, то раскладка полей в памяти будет как в С. Если есть виртуальные функции — то "внутри" объекта появятся указатели на таблицы виртуальных функций (у наследников таких указателей может быть больше, чем 1)
А как иерархия может быть совместима с С?
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Здравствуйте, Erop, Вы писали:
E>Здравствуйте, npak, Вы писали:
M>>>Представьте себе иерархию классов.
N>>Может быть, предполагается совместимость с С? Если нет виртуальных функций, то раскладка полей в памяти будет как в С. Если есть виртуальные функции — то "внутри" объекта появятся указатели на таблицы виртуальных функций (у наследников таких указателей может быть больше, чем 1)
E>А как иерархия может быть совместима с С?
Вот выхолощенный пример, такой прием используется при описаниях протоколов на С
struct BaseHeader {
int f1;
int f2;
};
struct ExtendedHeader {
struct BaseHeader base;
int f3;
};
struct Message {
struct ExtendedHeader base;
int f4;
};
Если написать иерархию на С++
struct BaseHeader {
int f1;
int f2;
};
struct ExtendedHeader : public BaseHeader {
int f3;
};
struct Message : public ExtendedHeader {
int f4;
};
то типы этой иерархии будут разложены в памяти (memory layout) так же, как аналогичные типы из С. То есть сишная и плюсовая программа/функции смогут обмениваться бинарными данными. Но для этого необходимо, чтобы в С++ не было виртуальных функций, иначе раскладка полей в памяти будет перемешана с указателями на таблицы виртуальных функций.
Здравствуйте, Suna Bozzu, Вы писали:
SB>Возможно, для того, что бы не создавалась таблица виртуальных функций (для увеличения быстродействия). SB>Но, если изначально предполагалось использовать этот класс в качестве базового, то такое решение очень спорное (на мой взгляд).
Ничего страшного, можно обезопаситься через объявление деструктора как protected.
Здравствуйте, Muxa, Вы писали: M>Представьте себе иерархию классов. M>Разработчик самого базового из них не сделал деструктор виртуальным. M>Для чего он мог это сделать?
Не знаю, для чего он это сделал, но точно знаю почему такое есть у меня
Потому что экземпляры производных классов создаются исключительно в стеке.
Поэтому виртуальный деструктор — даром не нужен.
Скрытый текст
/// <summary>
/// Интерфейс контекста операции с базой данных.
/// </summary>
//! Класс не предназначен для динамических объектовclass t_db_operation_context
{
public:
/// <summary>
/// Получение указателя на низкоуровневую транзакцию операции
/// </summary>
//! \return
//! Может быть NULLvirtual t_db_transaction* get_transaction()=0;
/// <summary>
/// Получение сервисного объекта
/// </summary>
//! \param[in] rguidService
//! \return
//! NULL, если сервис с таким идентификатором не доступенvirtual t_db_object_ptr get_service(REFGUID rguidService)=0;
};//class t_db_operation_context
Кстати, от него наследуется еще один абстрактный класс
Скрытый текст
/// <summary>
/// Контекст операции с базой данных
/// </summary>class t_ibp_operation_context:public db_obj::t_db_operation_context
{
private:
typedef t_ibp_operation_context self_type;
t_ibp_operation_context(const self_type&);
self_type& operator = (const self_type&);
public:
t_ibp_operation_context(){;}
//interface ------------------------------------------------------------
/// <summary>
/// Получение ibp-транзакции в рамках которой выполняется операция
/// </summary>
//! \return
//! Can be NULLvirtual t_ibp_transaction* ibp_transaction()=0;
/// <summary>
/// Получение ibp-команды для которой выполняется операция
/// </summary>
//! \return
//! Can be NULLvirtual t_ibp_command* ibp_command()=0;
};//class t_ibp_operation_context
А уже от него — собственно сама реализация
Скрытый текст
/// <summary>
/// Реализация IBP-контекста для операций с базой данных
/// </summary>
//!
//! \attention
//! Методы интерфейса t_db_operation могут вызываться асинхронно.class t_ibp_operation_context_std
:public t_ibp_operation_context,
public IBP_DEF_DB_INTERFACE_IMPL_STATIC(db_obj::t_db_operation)
{
private:
typedef t_ibp_operation_context_std self_type;
t_ibp_operation_context_std(const self_type&);
self_type& operator = (const self_type&);
public: //typedefs ------------------------------------------------------
/// <summary>
/// Сигнальный тип, используемый для конструирования контекста для
/// операции получения метаданных
/// </summary>struct for_meta_op{};
class tag_attach_to_command;
public:
/// <summary>
/// Конструктор инициализации
/// </summary>
//! \param[in] transaction
//! \param[in] command
t_ibp_operation_context_std(t_ibp_transaction* transaction,
t_ibp_command* command);
/// <summary>
/// Конструктор инициализации
/// </summary>
//! \param[in] transaction
//! \param[in] command
//! \param[in] pSession
//! Not NULL. Используется для создания временной транзакции
//! \param[in] op_flags
t_ibp_operation_context_std(t_ibp_transaction* transaction,
t_ibp_command* command,
TIBPSession* pSession,
TIBP_OperationFlags op_flags);
/// <summary>
/// Конструктор инициализации контекста для получения метаданных
/// </summary>
//! \param[in] tag
//! \param[in] transaction
//! \param[in] pSession
t_ibp_operation_context_std(for_meta_op tag,
t_ibp_transaction* transaction,
TIBPSession* pSession);
/// <summary>
/// Деструктор
/// </summary>
~t_ibp_operation_context_std();
//interface ------------------------------------------------------------
/// <summary>
/// Завершение работы. Выполняем коммит автоматической транзакции.
/// </summary>void complete();
/// <summary>
/// Установка новой команды
/// </summary>
//! \param[in] command
//! Can be NULL
//!
//! \attention
//! Если command!=NULL, то контекст не должен быть привязан к другой команде.void set_ibp_command(t_ibp_command* command);
//t_db_operation interface ---------------------------------------------
/// <summary>
/// Отмена операции. Вызывается асинхронно.
/// </summary>virtual void cancel();//abstract
//t_db_operation_context interface -------------------------------------
/// <summary>
/// Получение указателя на низкоуровневую транзакцию операции
/// </summary>
//! \return
//! Может быть NULLvirtual t_db_transaction* get_transaction();//abstract
/// <summary>
/// Запрос сервисного объекта
/// </summary>
//! \param[in] rguidService
//! Идентификатор сервиса
//! \return
//! NULL, если сервис с запрошенным идентификатором не доступен.virtual t_db_object_ptr get_service(REFGUID rguidService);//abstract
//t_ibp_operation_context interface ------------------------------------
/// <summary>
/// Получение ibp-транзакции в рамках которой выполняется операция
/// </summary>
//! \return
//! Can be NULLvirtual t_ibp_transaction* ibp_transaction();
/// <summary>
/// Получение ibp-команды для которой выполняется операция
/// </summary>
//! \return
//! Can be NULLvirtual t_ibp_command* ibp_command();
private: //typedefs -----------------------------------------------------typedef t_db_service_descr svc_descr_type;
typedef t_db_service_descr::tag_less svc_descr_less_type;
typedef t_db_memory_allocator allocator_type;
typedef t_tree_avl<svc_descr_type,
svc_descr_less_type,
allocator_type> services_type;
private:
typedef structure::t_multi_thread_traits thread_traits;
typedef thread_traits::guard_type guard_type;
typedef thread_traits::lock_guard_type lock_guard_type;
private:
//! \brief Указатель на связанную ibp-транзакцию
t_ibp_transaction_ptr const m_transaction;
//! \brief Указатель на связанную ibp-команду
t_ibp_command_ptr m_command;
TIBPSessionCPtr const m_cspSession;
TIBP_OperationFlags const m_op_flags;
private:
//! \brief Кэш сервисов.
services_type m_services;
private:
//! \brief Блокировщик временной транзакции
t_ibp_auto_transaction_locker m_TmpTrans;
private:
//! \brief Защита указателя на текущую операцию
guard_type m_cur_op_guard;
//! \brief Сигнал отмены операции
thread_traits::int_type m_cancel_flag;
//! \brief Текущая операция
//! \todo Заменить на t_db_operation_ptr
t_ibp_command_ptr m_cur_op;
};//class t_ibp_operation_context_std
Типа вот.
-- Пользователи не приняли программу. Всех пришлось уничтожить. --
Здравствуйте, 24, Вы писали: КД>>Потому что экземпляры производных классов создаются исключительно в стеке. 24>Виртуальный деструктор в этом случае привнесёт какие-то недостатки?
Да нет, вроде. Я так полагаю, компилятор (когда будет вызывать ~t_ibp_operation_context_std) вообще к VT обращаться не будет — тип объекта же и так ясен.
Но определять вируальный деструктор в базовом классе — все равно не тянет
Вот если бы это был базовый класс для динамических (или как там они называются) объектов, то виртуальный деструктор появился бы. Кстати ... а нету его там нифига — он появляется в производном классе, который реализует метод release с его delete this. И ничего — живем!
Скрытый текст
/// <summary>
/// Base class for smart interface.
/// </summary>template<class tag_thread_traits>
class COMP_CONF_DECLSPEC_NOVTABLE t_basic_smart_interface
{
public: //typedefs ------------------------------------------------------typedef tag_thread_traits thread_traits;
typedef typename thread_traits::int_type cnt_ref_type;
public:
virtual cnt_ref_type add_ref()=0;
virtual cnt_ref_type release()=0;
};//class t_basic_smart_interface
-- Пользователи не приняли программу. Всех пришлось уничтожить. --
N>то типы этой иерархии будут разложены в памяти (memory layout) так же, как аналогичные типы из С. То есть сишная и плюсовая программа/функции смогут обмениваться бинарными данными. Но для этого необходимо, чтобы в С++ не было виртуальных функций, иначе раскладка полей в памяти будет перемешана с указателями на таблицы виртуальных функций.
В реальном продукте я был бы против кода, основанного на таких предположениях.
Здравствуйте, datura-inoxia, Вы писали:
DI>Здравствуйте, Muxa, Вы писали:
M>>Представьте себе иерархию классов. M>>Разработчик самого базового из них не сделал деструктор виртуальным. M>>Для чего он мог это сделать?
DI>Например, чтобы запретить удаление по указателю на базовый класс. DI>Это может быть полезно, например, при передаче указателя на базовый класс в качестве callback'a куда-нибудь. Чтобы в этом где-нибудь, могли вызывать методы интерфейса, но не могли удалять объект.
тут вроде речь идет о невиртуальном деструкторе. А для того, о чем ты пишешь, деструктор должен быть protected (и ты сам в конце об этом же говоришь). DI>Чтобы выразить это явно, а не превращать увлекательную отладку странных глюков, деструктор нужно объявить как protected.А вот какое отношение имеет одно (невиртуальность) к другому (протектнутости) я честно говоря не уловил.
ЗЫ. сам довольно часто использую невиртуальные public деструкторы. Т.к. в большинстве случаев объект (базового класса или производного — без разницы) является автоматическим (стековым) объектом. И только если предполагается удалять объект по указателю на базовый класс — вот только тогда делаю деструктор базового класса виртуальным. Но прежде чем так сделать сперва хорошенько подумаю, а нельзя ли решить задачу используя автоматические объекты. И только если уж совсем никак и без удаления по указателю на базовый класс не обойтись — ну тогда виртуальный деструктор в руки .
R>Причем деструктор этого абстрактного класса я намеренно объявляю невиртуальным и защищенным, тем самым как бы подчеркивая, что стратегия владения объектами выбирается разработчиком производных классов. При этом у разработчика производного класса есть две возможности: 1) (традиционный) сделать деструктор производного класса открытым и виртуальным; 2) сделать деструктор невиртуальным и защищенным, а для создания объектов реализовать фабричный метод.
R>Пример второго варианта: R>
R>А вот теперь замечательный момент: умный указатель производного типа, возвращаемый фабричным методом create_instance, легко преобразуется в умный указатель абстрактного типа. Это позволяет владеть объектами и корректно удалять их полиморфно: R>
Здравствуйте, 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();
А теперь немного повторюсь. Ценность этого подхода в том, мухи отделены от котлет. Те части программы, которые используют полиморфные объекты, не интересуются ни способом их создания, ни областью памяти, в которой хранятся эти объекты, ни временем их жизни. Так, в этом примере два объекта расположены в динамической памяти, а один — во временной памяти функции main. Но внутри функции use, использующей эти объекты, все эти детали не интересны. А защищенный деструктор в абстрактном базовом классе дает нам защищенность от очумелых ручек, способных написать что угодно, в т.ч. и такое: