Подтвердить что ты не робот

Вызов виртуальной функции из деструктора

Это безопасно?

class Derived:  public PublicBase, private PrivateBase
{
 ... 

   ~Derived()
   {
      FunctionCall();
   }

   virtual void FunctionCall()
   {
      PrivateBase::FunctionCall();
   }
}

class PublicBase
{
   virtual ~PublicBase(){};
   virtual void FunctionCall() = 0;
}

class PrivateBase
{
   virtual ~PrivateBase(){};
   virtual void FunctionCall()
   {
    ....
   }
}


PublicBase* ptrBase = new Derived();
delete ptrBase;

Этот код разбивает иногда на IP-адрес в плохом адресе.

Это нехорошая идея, чтобы вызвать виртуальную функцию в конструкторе, понятна для всех.

Из таких статей, как http://www.artima.com/cppsource/nevercall.html Я понимаю, что деструктор также не так хорош для вызова виртуальной функции.

Мой вопрос: "Это правда?" Я тестировал VS2010 и VS2005 и вызывается PrivateBase:: FunctionCall. Является ли поведение undefined?

4b9b3361

Ответ 1

Я собираюсь пойти против потока здесь... но сначала я должен предположить, что ваш деструктор PublicBase является виртуальным, иначе деструктор Derived никогда не будет вызываться.

Обычно не рекомендуется вызывать виртуальную функцию из конструктора/деструктора

Причиной этого является то, что динамическая отправка странная во время этих двух операций. Фактический тип объекта изменяется во время строительства и он снова изменяется во время разрушения. Когда деструктор выполняется, объект имеет именно тот тип и никогда не является производным от него. Динамическая диспетчеризация действует в любое время, но конечный переадресатор виртуальной функции будет меняться в зависимости от того, где вы находитесь в иерархии.

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

Но

В вашем конкретном случае конечный переопределитель (по крайней мере, для этой части иерархии) находится выше вашего уровня. Более того, вы вообще не используете динамическую отправку. Вызов PrivateBase::FunctionCall(); статически разрешен и фактически эквивалентен вызову любой не виртуальной функции. Тот факт, что функция виртуальна или нет, не влияет на этот вызов.

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

Ответ 2

Это безопасно?

Да. Вызов виртуальной функции от конструктора или деструктора отправляет эту функцию, как если бы динамический тип объекта был в данный момент сконструирован или уничтожен. В этом случае он вызывается из деструктора Derived, поэтому он отправляется в Derived::FunctionCall (который в вашем случае вызывает PrivateBase::FunctionCall не виртуально). Все это хорошо определено.

"Неплохая идея" вызывать виртуальные функции от конструктора или деструктора по трем причинам:

  • Это вызовет неожиданное поведение, если вы вызываете его из базового класса и (ошибочно) ожидаете, что он будет отправлен на переопределение в производном классе;
  • Это приведет к поведению undefined, если оно чисто виртуальное;
  • Вам придется объяснять свое решение тем людям, которые считают, что это всегда неправильно.

Ответ 3

В целом, не рекомендуется вызывать виртуальную функцию, если объект класса, который он может отправить (т.е. "полный" объект самого производного класса), полностью построен. И это не так.

  • пока все конструкторы не закончат выполнение
  • после завершения любого деструктора

Ответ 4

Это очень плохая идея в соответствии со скоттом: ссылка

Это то, что я собрал и выполнил, чтобы помочь мне лучше понять процесс уничтожения, вы также можете найти его полезным

#include <iostream>
using namespace std;


class A {
public:
  virtual void method() {
    cout << "A::method" << endl;
  }

  void otherMethod() {
    method();
  }

  virtual ~A() {
    cout << "A::destructor" << endl;
    otherMethod();
  }

};

class B : public A {
public:
  virtual void method() {
    cout << "B::method" << endl;
  }

  virtual ~B() {
    cout << "B::destructor" << endl;
  }
};

int main() {

  A* a = new B();

  a->method();

  delete a;

}

Ответ 5

  Это безопасно?

Да и нет.

Да, потому что ваш пример как есть хорошо определен и будет работать. Все это хорошо объясняется другими ответами. Кроме того, этот код абсолютно безопасен, потому что он не будет компилироваться так, как он написан: частные dtors в базовых классах и т.д.

Причина, по которой ваш пример не безопасен, заключается в том, что этот код предполагает, что никто другой не будет переопределять FunctionCall из вашего класса Derived , и не будет ожидать, что переопределение будет вызвано при уничтожении объекта, Скорее всего компиляторы будут жаловаться на это. Вы можете улучшить свой код, отметив FunctionCall как окончательный:

class Derived : public PublicBase, private PrivateBase
{
 ... 
   virtual void FunctionCall() final;
}

или ваш класс Derived как окончательный:

class Derived final : public PublicBase, private PrivateBase
{
 ... 
   virtual void FunctionCall();
}

Если вы компилируете на старых компиляторах или не можете использовать c ++ 11 по какой-либо другой причине, тогда вы можете, по крайней мере, более четко указать здесь и написать в своем коде именно то, что произойдет во время выполнения, независимо от того, если FunctionCall переопределяется любым потомком вашего класса Derived:

class Derived : public PublicBase, private PrivateBase
{
 ... 
   ~Derived()
   {
      Derived::FunctionCall(); // be explicit which FunctionCall will be called
   }

   virtual void FunctionCall()
   {
      PrivateBase::FunctionCall();
   }
}