web-dev-qa-db-fra.com

Appel d'une fonction virtuelle depuis un destructeur

Est-ce sûr?

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;

Ce code casse parfois avec IP dans une mauvaise adresse.

Ce n'est pas une bonne idée d'appeler une fonction virtuelle sur constructeur est clair pour tout le monde.

D'après des articles comme http://www.artima.com/cppsource/nevercall.html Je comprends que le destructeur est également un endroit pas si bon pour appeler une fonction virtuelle.

Ma question est "Est-ce vrai?" J'ai testé avec VS2010 et VS2005 et PrivateBase :: FunctionCall est appelé. Le comportement est-il indéfini?

36
cprogrammer

Je vais aller à contre-courant ici ... mais d'abord, je dois supposer que votre destructeur PublicBase est virtuel, sinon le destructeur Derived ne sera jamais appelé.

Ce n'est généralement pas une bonne idée d'appeler une fonction virtuelle à partir d'un constructeur/destructeur

La raison en est que la répartition dynamique est étrange lors de ces deux opérations. Le type réel de l'objet change pendant la construction et il change à nouveau pendant destruction. Lorsqu'un destructeur est en cours d'exécution, l'objet est exactement de ce type, et jamais un type qui en dérive. La répartition dynamique est en vigueur à tout moment, mais le dérogation finale de la fonction virtuelle changera selon l'endroit où vous vous trouvez dans la hiérarchie.

Autrement dit, vous ne devez jamais vous attendre à ce qu'un appel à une fonction virtuelle dans un constructeur/destructeur soit exécuté dans n'importe quel type dérivé du type du constructeur/destructeur en cours d'exécution.

Mais

Dans votre cas particulier, le dérogateur final (au moins pour cette partie de la hiérarchie) est au-dessus votre niveau. De plus, vous n'utilisez pas du tout de répartition dynamique . L'appel PrivateBase::FunctionCall(); est résolu statiquement et équivaut effectivement à un appel à n'importe quelle fonction non virtuelle. Le fait que la fonction soit virtuelle ou non n'affecte pas cet appel.

Donc oui c'est bien de faire comme vous le faites, bien que vous serez obligé d'expliquer cela dans les revues de code car la plupart des gens apprennent le mantra de la règle plutôt que la raison.

Est-ce sûr?

Oui. L'appel d'une fonction virtuelle à partir d'un constructeur ou d'un destructeur distribue la fonction comme si le type dynamique de l'objet était celui en cours de construction ou de destruction. Dans ce cas, il est appelé depuis le destructeur de Derived, il est donc envoyé à Derived::FunctionCall (qui, dans votre cas, appelle PrivateBase::FunctionCall non virtuel). Tout cela est bien défini.

Ce n'est "pas une bonne idée" d'appeler des fonctions virtuelles à partir d'un constructeur ou d'un destructeur pour trois raisons:

  • Cela provoquera un comportement inattendu si vous l'appelez à partir d'une classe de base et (à tort) vous attendez à ce qu'il soit distribué à un remplacement dans une classe dérivée;
  • Il provoquera un comportement indéfini s'il est purement virtuel;
  • Vous devrez continuer d'expliquer votre décision à des gens qui croient que c'est toujours faux.
20
Mike Seymour

En général, ce n'est pas une bonne idée d'appeler une fonction virtuelle, à moins que l'objet de la classe à laquelle elle pourrait être envoyée (c'est-à-dire l'objet "complet" de la classe la plus dérivée) soit entièrement construit. Et ce n'est pas le cas

  • jusqu'à ce que tous les constructeurs finissent l'exécution
  • après la fin de l'exécution d'un destructeur
2
Grzegorz Herman

C'est une très mauvaise idée selon scott: link

Voici ce que j'ai compilé et exécuté pour m'aider à mieux comprendre le processus de destruction, vous pourriez également le trouver utile

#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;

}
1
sji