32 votes

Appel de la méthode virtuelle dans le constructeur de la classe de base

Je sais que l'appel d'une méthode virtuelle à partir d'un constructeur de classe de base peut être dangereux depuis la classe fille peut ne pas être dans un état valide. (au moins en C#)

Ma question est ce que si la méthode virtuelle est celui qui initialise l'état de l'objet ? Est il bon de le faire ou faut-il un processus en deux étapes, d'abord à créer l'objet et ensuite à la charge de l'état ?

Première option: (en utilisant le constructeur pour initialiser l'état)

public class BaseObject {
    public BaseObject(XElement definition) {
        this.LoadState(definition);
    }

    protected abstract LoadState(XElement definition);
}

Deuxième option: (à l'aide d'un processus en deux étapes)

public class BaseObject {
    public void LoadState(XElement definition) {
        this.LoadStateCore(definition);
    }

    protected abstract LoadStateCore(XElement definition);
}

Dans la première méthode, le consommateur, le code permet de créer et d'initialiser l'objet avec une seule instruction:

// The base class will call the virtual method to load the state.
ChildObject o = new ChildObject(definition)

Dans la deuxième méthode, le consommateur devra créer l'objet et à la charge de l'état:

ChildObject o = new ChildObject();
o.LoadState(definition);

39voto

Jon Skeet Points 692016

(Cette réponse s'applique à C# et Java. Je crois que C++ fonctionne différemment sur ce sujet).

L'appel d'une méthode virtuelle dans un constructeur est en effet dangereux, mais parfois, il peut se retrouver avec le plus propre code.

Je voudrais essayer d'éviter, lorsque cela est possible, mais sans courber le design très. (Par exemple, le "initialiser plus tard" option interdit l'immuabilité.) Si vous ne utilisez une méthode virtuelle dans le constructeur, document très fortement. Tant que tout le monde est conscient de ce qu'il fait, il ne devrait pas provoquer de trop nombreux problèmes. Je voudrais essayer de limiter la visibilité si, comme vous l'avez fait dans votre premier exemple.

EDIT: Une chose qui est important ici, c'est qu'il y a une différence entre C# et Java, dans l'ordre de l'initialisation. Si vous avez une classe telle que:

public class Child : Parent
{
    private int foo = 10;

    protected override void ShowFoo()
    {
        Console.WriteLine(foo);
    }
}

où l' Parent constructeur appelle ShowFoo, en C#, il affiche 10. Le programme équivalent en Java devrait afficher 0.

10voto

Greg Rogers Points 18119

En C++, l'appel d'une méthode virtuelle dans une classe de base constructeur de simplement appeler la méthode comme si la classe dérivée n'existe pas encore (car il n'a pas). Cela signifie donc que l'appel est résolu au moment de la compilation pour quelle que soit la méthode, il doit appeler dans la classe de base (ou les classes dérivées à partir de).

Testé avec GCC, il vous permet d'appeler une fonction virtuelle pure à partir d'un constructeur, mais il donne un avertissement, et les résultats dans un lien à l'erreur. Il semble que ce comportement n'est pas défini par la norme:

"Fonctions de membre peut être appelé à partir d'un constructeur (ou destructeur) d'une classe abstraite; l'effet de faire un appel virtuel (de la classe.virtuel) à une fonction virtuelle pure directement ou indirectement à l'objet en cours de création (ou détruit) à partir d'un tel constructeur (ou destructeur) n'est pas défini."

4voto

Rob Walker Points 25840

Avec C++ les méthodes virtuelles sont acheminés à travers la vtable pour la classe qui est en cours de construction. Donc dans votre exemple, il serait de générer une méthode virtuelle pure exception puisque tout BaseObject est en cours de construction il n'y a pas LoadStateCore méthode à invoquer.

Si la fonction n'est pas abstrait, mais ne font rien, alors vous aurez souvent le programmeur se gratter la tête en essayant de se rappeler pourquoi il est que la fonction n'est pas appelée.

Pour cette raison, vous ne pouvez tout simplement pas faire de cette façon en C++ ...

4voto

Brian Points 2013

Pour le C++, le constructeur de base est appelée avant que la dérivée constructeur, ce qui signifie que la table virtuelle qui contient les adresses de la classe dérivée est substituée fonctions virtuelles) n'existe pas encore. Pour cette raison, il est considéré comme une chose TRÈS dangereuse à faire (surtout si les fonctions sont virtuelle pure dans la classe de base...ce sera la cause d'une pure virtuel exception).

Il y a deux façons de contourner cela:

  1. Faire un processus en deux étapes de la construction + initialisation
  2. Déplacer les fonctions virtuelles pour une classe interne qui vous permet de mieux contrôler (pouvez utiliser l'approche décrite ci-dessus, voir l'exemple pour plus de détails)

Un exemple de (1) est:

class base
{
public:
    base()
    {
      // only initialize base's members
    }

    virtual ~base()
    {
      // only release base's members
    }

    virtual bool initialize(/* whatever goes here */) = 0;
};

class derived : public base
{
public:
    derived ()
    {
      // only initialize derived 's members
    }

    virtual ~derived ()
    {
      // only release derived 's members
    }

    virtual bool initialize(/* whatever goes here */)
    {
      // do your further initialization here
      // return success/failure
    }
};

Un exemple de (2) est:

class accessible
{
private:
    class accessible_impl
    {
    protected:
        accessible_impl()
        {
          // only initialize accessible_impl's members
        }

    public:
        static accessible_impl* create_impl(/* params for this factory func */);

        virtual ~accessible_impl()
        {
          // only release accessible_impl's members
        }

        virtual bool initialize(/* whatever goes here */) = 0;
    };

    accessible_impl* m_impl;

public:
    accessible()
    {
        m_impl = accessible_impl::create_impl(/* params to determine the exact type needed */);

        if (m_impl)
        {
            m_impl->initialize(/* ... */);  // add any initialization checking you need
        }
    }

    virtual ~accessible()
    {
        if (m_impl)
        {
            delete m_impl;
        }
    }

    /* Other functionality of accessible, which may or may not use the impl class */
};

Approche (2) utilise le modèle de Fabrique de la mise en œuvre de l' accessible de la classe (qui fournira la même interface que votre base de la classe). L'un des principaux avantages est que vous obtenez de l'initialisation lors de la construction de accessible qui est en mesure de faire usage de membres virtuels de accessible_impl en toute sécurité.

3voto

David Thornley Points 39051

Pour le C++, l'article 12.7, paragraphe 3 de la Norme couvre ce cas.

Pour résumer, c'est légal. Il permettra de résoudre à la bonne fonction du type de constructeur en cours d'exécution. Par conséquent, l'adaptation de votre exemple de syntaxe C++, vous seriez appelant BaseObject::LoadState(). Vous ne pouvez pas obtenir à l' ChildObject::LoadState(), et d'essayer de le faire en spécifiant la classe ainsi que les résultats de la fonction de comportement indéfini.

Les constructeurs de classes abstraites sont couverts dans la section 10.4, paragraphe 6. En bref, ils peuvent appeler des fonctions de membre, mais l'appel d'une fonction virtuelle pure dans le constructeur est un comportement indéfini. Ne pas le faire.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X