Нужен ли виртуальный деструктор для вашего интерфейса, если вы всегда храните его в shared_ptr?

Поскольку boost::/std::shared_ptr имеет преимущество удаления типа удаления, вы можете делать такие приятные вещи, как

#include <memory>

typedef std::shared_ptr<void> gc_ptr;

int main(){
  gc_ptr p1 = new int(42);
  gc_ptr p2 = new float(3.14159);
  gc_ptr p3 = new char('o');
}

И это правильно удалит весь указатель благодаря сохранению правильного удалителя.

Если вы гарантируете, что каждая реализация вашего интерфейса всегда создается с помощью shared_ptr<Interface> (или make_shared<Interface>), действительно ли вам нужен деструктор virtual? Я бы объявил это virtual в любом случае, но я просто хочу знать, поскольку shared_ptr всегда удаляет тип, которым он был инициализирован (если не указан другой настраиваемый удалитель).


person Xeo    schedule 09.07.2011    source источник
comment
возможный дубликат магии shared_ptr :)   -  person Armen Tsirunyan    schedule 09.07.2011
comment
@Armen: Это не дубликат, он не спрашивает, как shared_ptr это делает, а нужно ли использовать виртуальный деструктор зная, что shared_ptr творит магию.   -  person David Rodríguez - dribeas    schedule 09.07.2011
comment
@ Дэвид: Нет, он этого не делает. Он говорит, что все равно будет использовать виртуальный деструктор. Он спрашивает, нормально ли не иметь такой. Так это дубликат   -  person Armen Tsirunyan    schedule 09.07.2011
comment
Да, это правда. Однако лично я бы побеспокоился об этом. Однажды я решу, что shared_ptr не нужен, я просто использую указатель на базовый класс, и все незаметно ломается. Я бы расценил это как хрупкий код, который внешний код, делающий разумные предположения о том, как реализованы классы, может легко сломаться, и не сделаю этого, если я не смогу доказать, что существует требование, которое может быть выполнено только таким образом.   -  person jcoder    schedule 09.07.2011
comment
Что ж ... если у вас есть shared_ptr экземпляры, которые могут указывать на производный тип, тогда вы почти наверняка используете virtual функции-члены, верно? Иначе какой смысл? Так что в этом случае создание виртуального деструктора ничего не стоит ... кроме, может быть, небольшого дополнительного набора текста :)   -  person Karl Knechtel    schedule 09.07.2011
comment
Действительно ли в вашем примере нетривиально используется стирание типа shared_ptr? Удаление во всех трех случаях равно delete(void*). Я не вижу связи между настраиваемым средством удаления и указателем на удаление через базовый указатель.   -  person Kerrek SB    schedule 09.07.2011
comment
@Kerrek: Нет, удалитель во всех трех случаях разный. Все они могут взять void*, но привести его к правильному типу, int, float и char соответственно.   -  person Xeo    schedule 09.07.2011


Ответы (1)


Я бы по-прежнему следовал общему правилу для классов, которые должны быть производными:

Предоставьте публичный виртуальный деструктор или защищенный невиртуальный деструктор.

Причина в том, что вы не можете контролировать все виды использования, и это простое правило означает, что компилятор будет отмечать, если вы попытаетесь delete пройти неправильный уровень в иерархии. Учтите, что shared_ptr не гарантирует, что он вызовет соответствующий деструктор, только то, что он вызовет деструктор статического типа, который использовался в качестве аргумента:

base* foo();
shared_ptr<base> p( foo() );

Если base имеет общедоступный не виртуальный деструктор и foo возвращает тип, производный от base, то shared_ptr не сможет вызвать правильный деструктор. Если деструктор base виртуальный, все будет хорошо, если он защищен, компилятор сообщит вам, что там ошибка.

person David Rodríguez - dribeas    schedule 09.07.2011
comment
Я бы все равно объявил его виртуальным, [...]. :) Хороший момент в том, что невозможно контролировать все точки создания экземпляров. Хотя вы всегда можете обойтись именованным конструктором, но это, вероятно, выглядит не так хорошо. - person Xeo; 09.07.2011
comment
Предупреждение: защищенные деструкторы в настоящее время не отвечают «истина» для is_nothrow_destructible<T>::value, даже если они не генерируют исключение. По этой причине я предпочел бы публичный вариант. - person Howard Hinnant; 09.07.2011
comment
@Howard: спасибо за информацию о is_nothrow_destructible. Кажется, поступил правильно. Почему вы позволите тому факту, что в настоящее время он правильно сообщает о неразрушаемости для неразрушаемой штуки, заставили вас изменить ее на разрушаемую? - person Cheers and hth. - Alf; 09.07.2011
comment
@Xeo: Вы действительно можете контролировать создание экземпляров? Если ваш класс должен быть базовым, это означает, что я могу написать собственное расширение, и вы не можете контролировать, как я разрешаю пользователям создавать экземпляры моих объектов. - person David Rodríguez - dribeas; 09.07.2011
comment
@ DavidRodríguez-dribeas В конце концов, другие программисты всегда могут делать то, что хотят. Лучшее, что вы действительно можете сделать, - это задокументировать политику и препятствия для попыток обойти. - person Deduplicator; 11.08.2020