Re[3]: Зачем нужен невиртуальный деструктор?
От: minorlogic Украина  
Дата: 09.12.10 19:57
Оценка:
Здравствуйте, Muxa, Вы писали:

M>>Чтобы спрашивать это на собеседовании и выявлять джуниоров.

M>сомневаюсь что задумка создателей языка была именно такая.

задумка тут не причем , я попытался представить как такой вопрос может возникнуть.
... << RSDN@Home 1.2.0 alpha 4 rev. 1237>>
Ищу работу, 3D, SLAM, computer graphics/vision.
Re[4]: Зачем нужен невиртуальный деструктор?
От: Muxa  
Дата: 09.12.10 21:03
Оценка:
M>задумка тут не причем , я попытался представить как такой вопрос может возникнуть.
у человека, который не программировал на плюсах, еще и не такие вопросы могут возникнуть
Re[4]: Зачем нужен невиртуальный деструктор?
От: rg45 СССР  
Дата: 09.12.10 23:00
Оценка: 10 (1) +1
Здравствуйте, 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>};


Как так, нет деструктора? Так не бывает. Пусть сгенерированный компилятором, но деструктор есть всегда. И в данном случае он тоже есть, его можно явно вызвать:
#include <iostream>
#include <cstdlib>

template<typename TAllocator> 
struct CAllocatedOn 
{
  void* operator new( size_t s ) { return TAllocator::Alloc( s ); }
  void operator delete( void* p ) { TAllocator::Free( p ); }
};

struct Allocator
{
  static void* Alloc(size_t s)  { return malloc(s); }
  static void Free(void* p)  { free(p); }
};

int main()
{
  typedef CAllocatedOn<Allocator> Foo;
  Foo foo;
  foo.~Foo();
}
--
Справедливость выше закона. А человечность выше справедливости.
Re[5]: немного оффтопа
От: rg45 СССР  
Дата: 10.12.10 08:55
Оценка:
Здравствуйте, rg45, Вы писали:

R>Как так, нет деструктора? Так не бывает...


То, что у всех типов, даже у скалярных, формально есть деструктор можно показать так:
template<typename T>
void foo(T t)
{
  t.~T();
}

int main()
{
  foo(42);
}

Но вот что интересно, можно сделать так:
typedef int Foo;
int i = 42;
i.~Foo(); //Ok

но почему-то нельзя сделать так:
int i = 42;
i.~int(); //error: expected an identifier

--
Справедливость выше закона. А человечность выше справедливости.
Re[5]: Зачем нужен невиртуальный деструктор?
От: Erop Россия  
Дата: 10.12.10 13:36
Оценка: +2
Здравствуйте, rg45, Вы писали:

E>>А то я легко могу придумать тебе пример класса, который базовый, но вообще без деструктора

R>Как так, нет деструктора? Так не бывает. Пусть сгенерированный компилятором, но деструктор есть всегда. И в данном случае он тоже есть, его можно явно вызвать:
Ну да. Прошу прощения, это просто жаргон. "без деструктора" надо читать, как "с тривиальным деструктором" конечно же. Это если точно и грамотно выражаться. Что на форуме для новичков С++ наверное правильно.

Спасибо за замечание.
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Re[5]: Не единым аллокатором жив С++, в отличии от... ;)
От: Erop Россия  
Дата: 10.12.10 13:50
Оценка: +1
Здравствуйте, ankf, Вы писали:

A>Помоему при создании класса основные "трудозатраты" это работа менеджера памяти и выделение куска. А косвенный переход через виртуальную таблицу — это брызги для моряков по сравнению с выделением памяти и копированием.


Бывают массивы объектов, бывают пулы блоков памяти, бывают автоматические объекты, наконец
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Re[6]: немного оффтопа
От: Erop Россия  
Дата: 10.12.10 13:54
Оценка:
Здравствуйте, rg45, Вы писали:

R>То, что у всех типов, даже у скалярных, формально есть деструктор можно показать так:

R>

На самом деле это не так. Просто доля параметров шаблонов и псевдонимов имён есть специальный синтаксис, похожий на вызовы конструкторов и деструкторов.
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Re[7]: немного оффтопа
От: rg45 СССР  
Дата: 10.12.10 14:27
Оценка:
Здравствуйте, Erop, Вы писали:

E>На самом деле это не так. Просто доля параметров шаблонов и псевдонимов имён есть специальный синтаксис, похожий на вызовы конструкторов и деструкторов.


Ну, что касается шаблонов, это я еще понимаю. Но в связи с чем к псевдонимам типов применяются правила, отличные от правил, применяемым к оригинальным именам? Чем обосновано, и где в стандарте то место, по которому следует что так можно:
typedef int Foo;
int i = 42;
i.~Foo();

а так нельзя:
int i = 42;
i.~int();

--
Справедливость выше закона. А человечность выше справедливости.
Re[8]: немного оффтопа
От: uzhas Ниоткуда  
Дата: 10.12.10 14:38
Оценка: :)
Здравствуйте, rg45, Вы писали:

R>и где в стандарте то место, по которому следует что так можно:

R>а так нельзя:

аналогичный код
int x = int(); // OK
int* y = int*(); // FAIL

typedef int* intPtr;
int* z = intPtr(); // OK
Re[8]: немного оффтопа
От: Erop Россия  
Дата: 10.12.10 15:03
Оценка:
Здравствуйте, 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>

А про нельзя нет. Просто такой конструкции нет в стандарте.
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Re: Зачем нужен невиртуальный деструктор?
От: npak Россия  
Дата: 11.12.10 00:10
Оценка:
Здравствуйте, Muxa, Вы писали:

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

M>Разработчик самого базового из них не сделал деструктор виртуальным.
M>Для чего он мог это сделать?

Может быть, предполагается совместимость с С? Если нет виртуальных функций, то раскладка полей в памяти будет как в С. Если есть виртуальные функции — то "внутри" объекта появятся указатели на таблицы виртуальных функций (у наследников таких указателей может быть больше, чем 1)
Re[2]: Зачем нужен невиртуальный деструктор?
От: Erop Россия  
Дата: 11.12.10 00:38
Оценка:
Здравствуйте, npak, Вы писали:

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


N>Может быть, предполагается совместимость с С? Если нет виртуальных функций, то раскладка полей в памяти будет как в С. Если есть виртуальные функции — то "внутри" объекта появятся указатели на таблицы виртуальных функций (у наследников таких указателей может быть больше, чем 1)


А как иерархия может быть совместима с С?
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Re[3]: Зачем нужен невиртуальный деструктор?
От: npak Россия  
Дата: 11.12.10 08:09
Оценка: +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) так же, как аналогичные типы из С. То есть сишная и плюсовая программа/функции смогут обмениваться бинарными данными. Но для этого необходимо, чтобы в С++ не было виртуальных функций, иначе раскладка полей в памяти будет перемешана с указателями на таблицы виртуальных функций.
Re[2]: Зачем нужен невиртуальный деструктор?
От: vdimas Россия  
Дата: 11.12.10 08:31
Оценка:
Здравствуйте, Suna Bozzu, Вы писали:

SB>Возможно, для того, что бы не создавалась таблица виртуальных функций (для увеличения быстродействия).

SB>Но, если изначально предполагалось использовать этот класс в качестве базового, то такое решение очень спорное (на мой взгляд).

Ничего страшного, можно обезопаситься через объявление деструктора как protected.
Re: Зачем нужен невиртуальный деструктор?
От: Коваленко Дмитрий Россия http://www.ibprovider.com
Дата: 13.12.10 19:41
Оценка: 2 (1)
Здравствуйте, Muxa, Вы писали:

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

M>Разработчик самого базового из них не сделал деструктор виртуальным.
M>Для чего он мог это сделать?

Не знаю, для чего он это сделал, но точно знаю почему такое есть у меня

Потому что экземпляры производных классов создаются исключительно в стеке.

Поэтому виртуальный деструктор — даром не нужен.
  Скрытый текст
/// <summary>
///  Интерфейс контекста операции с базой данных.
/// </summary>
//! Класс не предназначен для динамических объектов
class t_db_operation_context
{
 public:
  /// <summary>
  ///  Получение указателя на низкоуровневую транзакцию операции
  /// </summary>
  //! \return
  //!  Может быть NULL
  virtual 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 NULL
  virtual t_ibp_transaction* ibp_transaction()=0;
  
  /// <summary>
  ///  Получение ibp-команды для которой выполняется операция
  /// </summary>
  //! \return
  //!  Can be NULL
  virtual 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
  //!  Может быть NULL
  virtual 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 NULL
  virtual t_ibp_transaction* ibp_transaction();
  
  /// <summary>
  ///  Получение ibp-команды для которой выполняется операция
  /// </summary>
  //! \return
  //!  Can be NULL
  virtual 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

Типа вот.
-- Пользователи не приняли программу. Всех пришлось уничтожить. --
Re[2]: Зачем нужен невиртуальный деструктор?
От: 24  
Дата: 13.12.10 19:49
Оценка:
Здравствуйте, Коваленко Дмитрий, Вы писали:

КД>Потому что экземпляры производных классов создаются исключительно в стеке.


Виртуальный деструктор в этом случае привнесёт какие-то недостатки?
Re[3]: Зачем нужен невиртуальный деструктор?
От: Коваленко Дмитрий Россия http://www.ibprovider.com
Дата: 13.12.10 20:09
Оценка:
Здравствуйте, 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
-- Пользователи не приняли программу. Всех пришлось уничтожить. --
Re[4]: Зачем нужен невиртуальный деструктор?
От: TimurSPB Интернет  
Дата: 13.12.10 20:26
Оценка:
N>то типы этой иерархии будут разложены в памяти (memory layout) так же, как аналогичные типы из С. То есть сишная и плюсовая программа/функции смогут обмениваться бинарными данными. Но для этого необходимо, чтобы в С++ не было виртуальных функций, иначе раскладка полей в памяти будет перемешана с указателями на таблицы виртуальных функций.

В реальном продукте я был бы против кода, основанного на таких предположениях.
Make flame.politics Great Again!
Re[2]: Зачем нужен невиртуальный деструктор?
От: programmater  
Дата: 14.12.10 16:47
Оценка:
Здравствуйте, datura-inoxia, Вы писали:

DI>Здравствуйте, Muxa, Вы писали:


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

M>>Разработчик самого базового из них не сделал деструктор виртуальным.
M>>Для чего он мог это сделать?

DI>Например, чтобы запретить удаление по указателю на базовый класс.

DI>Это может быть полезно, например, при передаче указателя на базовый класс в качестве callback'a куда-нибудь. Чтобы в этом где-нибудь, могли вызывать методы интерфейса, но не могли удалять объект.

тут вроде речь идет о невиртуальном деструкторе. А для того, о чем ты пишешь, деструктор должен быть protected (и ты сам в конце об этом же говоришь).
DI>Чтобы выразить это явно, а не превращать увлекательную отладку странных глюков, деструктор нужно объявить как protected.А вот какое отношение имеет одно (невиртуальность) к другому (протектнутости) я честно говоря не уловил.

ЗЫ. сам довольно часто использую невиртуальные public деструкторы. Т.к. в большинстве случаев объект (базового класса или производного — без разницы) является автоматическим (стековым) объектом. И только если предполагается удалять объект по указателю на базовый класс — вот только тогда делаю деструктор базового класса виртуальным. Но прежде чем так сделать сперва хорошенько подумаю, а нельзя ли решить задачу используя автоматические объекты. И только если уж совсем никак и без удаления по указателю на базовый класс не обойтись — ну тогда виртуальный деструктор в руки .
Re[2]: Зачем нужен невиртуальный деструктор?
От: bzzz  
Дата: 10.02.11 15:24
Оценка:
Здравствуйте, rg45, Вы писали:

R>
R>class IFoo
R>{
R>public:

R>  virtual void bar() = 0;
R>  virtual void baz() = 0;

R>protected:
R>  IFoo() { }
R>};
R>

R>Причем деструктор этого абстрактного класса я намеренно объявляю невиртуальным и защищенным, тем самым как бы подчеркивая, что стратегия владения объектами выбирается разработчиком производных классов. При этом у разработчика производного класса есть две возможности: 1) (традиционный) сделать деструктор производного класса открытым и виртуальным; 2) сделать деструктор невиртуальным и защищенным, а для создания объектов реализовать фабричный метод.

R>Пример второго варианта:

R>
R>using boost::shared_ptr;

R>class FooDerived1 : public IFoo
R>{
R>public:

R>  virtual void bar();
R>  virtual void baz();

R>  static shared_ptr<FooDerived1> create_instance(/*possible arguments*/);
R>  {
R>    return shared_ptr<FooDerived1>(new FooDerived(/*possible arguments*/));
R>  }

R>protected:
R>  FooDerived1();
R>};

R>class FooDerived2 : public IFoo { /*...*/ };
R>class FooDerived3 : public IFoo { /*...*/ };
R>

R>А вот теперь замечательный момент: умный указатель производного типа, возвращаемый фабричным методом create_instance, легко преобразуется в умный указатель абстрактного типа. Это позволяет владеть объектами и корректно удалять их полиморфно:
R>
R>int main()
R>{
R>  typedef std::vector<boost::shared_ptr<IFoo> > Items;

R>  Items items;
R>  items.push_back(FooDerived1::create_instance(/*possible arguments*/));
R>  items.push_back(FooDerived2::create_instance(/*possible arguments*/));
R>  items.push_back(FooDerived3::create_instance(/*possible arguments*/));

R>  //...
R>}
R>


Всё классно но ваш пример не работает, не могли бы вы написать работающий пример?
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.