Производный класс с не виртуальным деструктором

Существуют ли какие-либо обстоятельства, в которых законным для производного класса является деструктор не virtual? Деструктор не virtual означает, что класс не должен использоваться в качестве базового класса. Будет ли иметь деструктор не virtual производного класса действовать как слабая форма модификатора Java final?

Изменить: Меня особенно интересует случай, когда базовый класс производного класса имеет деструктор virtual.

Ответ 1

Существуют ли какие-либо обстоятельства, при которых это является законным для производного класс, чтобы иметь не виртуальный деструктор?

Да.

Не виртуальный деструктор означает, что класс не должен использоваться как базовый класс.

Не совсем; не виртуальный деструктор означает, что удаление экземпляра derived с помощью указателя base не будет работать. Например:

class Base {};
class Derived : public Base {};

Base* b = new Derived;
delete b; // Does not call Derived destructor!

Если вы не делаете delete указанным выше образом, это будет хорошо. Но если это так, то вы, вероятно, будете использовать композицию, а не наследование.

Будет ли иметь не виртуальный деструктор производного класса действовать как слабая форма финального модификатора Java?

Нет, потому что virtual -ness распространяется на производные классы.

class Base
{
public:
    virtual ~Base() {}
    virtual void Foo() {};
};

class Derived : public Base
{
public:
    ~Derived() {}  // Will also be virtual
    void Foo() {}; // Will also be virtual
};

В С++ 03 или ранее не существует встроенного языкового механизма для предотвращения подклассов (*). Это не так уж и важно, так как вы всегда должны предпочитать композицию над наследованием. То есть, используйте наследование, когда отношение "is-a" имеет больше смысла, чем истинное отношение "has-a".

(*) 'final' модификатор был введен в С++ 11

Ответ 2

Совершенно допустимо иметь базовый класс с не виртуальным деструктором, если вы никогда не будете называть delete на указателе Base-класса, указывающем на объект производного класса.

Следуйте рекомендациям Herb Sutter:

Guideline #: Только если производным классам необходимо вызвать базовую реализацию виртуальной функции, сделайте виртуальную функцию защищенной. Только для специального случая деструктора:

Указатель #: Деструктор базового класса должен быть открытым или виртуальным, или защищенным и не виртуальным.


Возможно, ваш вопрос на самом деле:
Должен ли Destructor в классе Derived быть виртуальным, если базовый класс Destructor является виртуальным?

Ответ НЕТ.
Если деструктор класса Base виртуальный, деструктор класса Derived уже неявно виртуальный, вам не нужно явно указывать его как виртуальную.

Ответ 3

Адресация последнего изменения:

Изменить: меня особенно интересует случай, когда базовый класс производного класса имеет виртуальный деструктор.

В этом случае деструктор производного класса будет виртуальным, независимо от того, добавляете ли вы ключевое слово virtual или нет:

struct base {
   virtual ~base() {}       // destructor is virtual
};
struct derived : base {
   ~derived() {}            // destructor is also virtual, because it is virtual in base
};

Это не ограничивается деструкторами, если в любой точке иерархии типов член функции объявлен виртуальным, все переопределения (а не перегрузки) этой же функции будут виртуальными, независимо от того, объявлены они так или нет. Конкретный бит для деструкторов заключается в том, что ~derived() переопределяет virtual ~base(), даже если имя члена отличается - это единственная специфика для деструкторов.

Ответ 4

Вы вопрос не совсем ясный. Если базовый класс имеет виртуальный деструктор, производный класс будет иметь один, независимо. Там нет способа чтобы отключить виртуальность после объявления.

И есть, конечно, случаи, когда имеет смысл вывести из класс, который не имеет виртуального деструктора. Причина, по которой база class destructor должен быть виртуальным, так что вы можете удалить его через указатель на базовый класс. Если вывод является закрытым, у вас нет беспокоиться об этом, так как ваш Derived* не будет преобразован в Base*. В противном случае я видел рекомендацию, что если базовый класс деструктор не является виртуальным, он должен быть защищен; это предотвращает одно случай поведения undefined (удаление указателя на базу), которое может произойти. На практике, однако, много базовых классов (например, std::iterator<>) имеют семантику такую, что она даже не возникает любой для создания указателей на них; а тем более указатели. Поэтому добавление защиты может быть большим, чем нужно.

Ответ 5

Зависит от цели вашего класса. Иногда бывает хорошей практикой сделать ваш деструктор защищенным, но не виртуальным, - в основном говорится: "Вы не должны удалять объект производного класса с помощью указателя базового типа"

Ответ 6

Не виртуальный деструктор отлично подходит, если вы не хотите использовать его в качестве базового указателя для производных классов при удалении объекта.

Если ваши производные классы полиморфны, передавая и сохраняя его с помощью базового указателя, а затем удаляя его, тогда ответ будет отрицательным, используйте виртуальный деструктор.

Ответ 7

Да, есть:

void dothis(Base const&);

void foo() {
  Derived d;
  tothis(d);
}

Здесь класс используется полиморфно, но delete не вызывается, поэтому он отлично.

Другим примером может быть:

std::shared_ptr<Base> create() { return std::shared_ptr<Base>(new Derived); }

потому что shared_ptr может использовать неполиморфный delete (через стирание типа).

Я реализовал предупреждение в Clang специально для обнаружения вызова delete на полиморфных не конечных классах с не виртуальными деструкторами, поэтому, если вы используете clang -Wdelete-non-virtual-dtor, он будет предупреждать специально для этого случая.

Ответ 8

Да, Нет и Нет.

Виртуальный деструктор не имеет ничего общего с способностью класса быть базой или производным классом. Это законный вариант как.

Тем не менее, есть определенные причины, чтобы сделать деструкторов виртуальными. См. Здесь: http://en.wikipedia.org/wiki/Virtual_destructor#Virtual_destructors. Это делает класс, между прочим, иметь виртуальную таблицу, если она уже не имеет этого. Однако для выполнения наследования для С++ для виртуальных таблиц не требуется.

Ответ 9

Если ваш производный класс не добавляет каких-либо членов данных в базовый класс и имеет пустой корпус деструктора, то не имеет значения, является ли деструктор виртуальным или нет - все производные деструкторы будут выполнять вызов один в любом случае. Это не рекомендуется, потому что слишком легко для кого-то прийти и изменить класс, не зная об этих ограничениях.

Если вы никогда не пытаетесь удалить объект с помощью указателя на базовый класс, вы будете в безопасности. Это еще одно правило, которое трудно обеспечить и должно использоваться с осторожностью.

Иногда у вас нет никакого контроля над базовым классом, и вы вынуждены извлекаться из него, хотя деструктор не является виртуальным.

Наконец, наличие не виртуального деструктора в базовом классе не накладывает никаких ограничений на производный класс, который будет применяться компилятором, поэтому я не думаю, что он вообще похож на Java final.

Ответ 10

Будет ли иметь не виртуальный деструктор производного класса действовать как слабая форма финального модификатора Java?

Совсем нет. Вот мое предложение запретить подкласс в С++ (например, последний модификатор на Java); сделать деструктор закрытым в классе. Тогда вы можете предотвратить создание подклассов из него

Ответ 11

Возможно, вы не захотите создать виртуальный деструктор в базовом классе? В этом случае деструктор не работает. Если вы используете указатель на базовый класс и создаете не виртуальный деструктор в родительском компиляторе, автоматически генерируете это предупреждение! Вы можете заблокировать его, если хотите создать конечный родительский класс. Но лучше всего отметить это как последнее:

class Base{
public:
    //No virtual destructor defined
    virtual void Foo() {};
};

class Derived final : public Base{
public:
    ~Derived() {}  // define some non-virtual destructor
    void Foo() {}; // Will also be virtual
};

В этом случае компилятор знает, чего вы хотите, и никаких предупреждений не генерируется. Решение с пустым виртуальным деструктором базы не слишком хорошо, но приемлемо. Вам не нужно устанавливать атрибут final. Но это не то, что вы хотите. Вам также не нужно определять пустой виртуальный базовый метод Foo Лучше всего:

class Base{
public:
  //No virtual destructor defined
  virtual void Foo() = 0; // abstract method
};
class Derived final : public Base{
public:
  ~Derived() {}  // define some non-virtual destructor
  void Foo() {}; // Will also be virtual
};

Это полный код для компилятора. Никаких предупреждений не генерируется и не используется запасной код.