2009-12-21 7 views
1

que je dois garder une liste (vecteur) des enfants pour chaque classe, et j'ai un problème délicat:Polymorphisme et l'héritage des membres statiques en C++

class A 
{ 
protected: 
    int a; 
    static vector<A*> children; 
public: 
    A(int a): a(a) {;}; 
    virtual void AddToChildren(A* obj); 
    virtual void ShowChildren(); 
    virtual void Show(); 
}; 

class B: public A 
{ 
protected: 
    int b; 
    static vector<A*> children; 
public: 
    B(int a, int b): b(b), A(a) { A::AddToChildren(this);}; 
    virtual void Show(); 
}; 

class C: public B 
{ 
protected: 
    int c; 

public: 
    C(int a, int b, int c): c(c), B(a,b) { B::AddToChildren(this);}; 
    virtual void Show(); 
}; 

vector<A*> A::children=vector<A*>(); 
vector<A*> B::children=vector<A*>(); 


void A::AddToChildren(A *obj) 
{ 
    children.push_back(obj); 
} 

void A::ShowChildren() 
{ 
    for(vector<A*>::iterator i=children.begin(); i!=children.end();i++) 
     (*i)->Show(); 
} 

Ajout A (0), B (1,1) et C (2,2,2) et appelant a.ShowChildren donne: 1,1; 2,2,2; 2,2,2

Chaque fois que je crée une instance de classe C, A :: children est mis à jour à la place de B :: children et A :: children. Sooo ... la classe C est ajoutée deux fois aux enfants de classe A, mais pas ajoutée à la classe B. Cela aide quand je copie la classe AddChildren (littéralement copie) à la classe B, de sorte que chaque classe ait ses propres AddChildren/ShowChildren. J'ai également réussi à accomplir cette tâche en utilisant des pointeurs, mais je me demande s'il y a un meilleur moyen. Je pense que le problème est quelque part dans "l'utilisation du bon vecteur", mais je ne sais pas comment forcer le compilateur à utiliser le bon.

Je serais reconnaissant pour toute suggestion sur ce que je fais mal ici. Tout d'abord, merci à tous pour vos commentaires et votre aide. En utilisant vos conseils (au sujet de ma conception et GetList virtuelle()) J'ai réussi à simplifier mon programme:

class A 
{ 
protected: 
    int a; 
    virtual vector<A*>* GetList(); 
public: 
    A(int a): a(a) {;}; 
    A(int a, A* inherited):a(a) { AddToChildren(inherited);}; 

    static vector<A*> children; 

    virtual void AddToChildren(A* obj); 
    virtual void ShowChildren(); 

    virtual void Show(); 
}; 

class B: public A 
{ 
protected: 
    int b; 
    virtual vector<A*>* GetList(); 
public: 
    static vector<A*> children; 
    B(int a, int b): b(b), A(a,this){;}; 
    B(int a, int b, A* inherited) : b(b), A(a,this){AddToChildren(inherited);}; 

    virtual void Show(); 

}; 

class C: public B 
{ 
protected: 
    int c; 
public: 
    C(int a, int b, int c): c(c), B(a,b,this) { }; 
    virtual void Show(); 
    virtual vector<A*>* GetList(); 
}; 


vector<A*> A::children=vector<A*>(); 
vector<A*> B::children=vector<A*>(); 


void A::AddToChildren(A *obj) 
{ 
    GetList()->push_back(obj); 
} 

void A::ShowChildren() 
{ 
    for(vector<A*>::iterator i=GetList()->begin(); i!=GetList()->end();i++) 
     (*i)->Show(); 
} 


vector<A*> * A::GetList() 
{ 

    return & children; 
} 

vector<A*> * B::GetList() 
{ 

    return & children; 
} 

vector<A*> * C::GetList() 
{ 

    return & children; 
} 

Maintenant, ses constructeurs en utilisant sans appeler la classe supérieure, il appelle juste le constructeur proprement dit de la classe supérieure. Ce n'est pas le meilleur, mais je pense que c'est mieux.

Une fois de plus, merci à tous pour votre aide.

Répondre

1

Votre intention de conception n'est pas suffisamment claire, c'est pourquoi les auteurs de certaines autres réponses ont confondu dans leurs réponses.

Dans votre code, vous semblez appeler AddToChildren depuis certains constructeurs mais pas avec les autres. Par exemple, vous avez une liste children dans A mais vous n'appelez jamais le constructeur AddToChildren de A::A. En outre, la classe C n'a pas sa propre liste children. Pourquoi? Est-il censé partager la liste children avec B?

Je peux deviner que le fait que vous n'appeliez pas AddToChildren de tous les constructeurs signifie que certains constructeurs sont destinés à construire des "objets" complets de type donné (ces constructeurs appellent AddToChildren), tandis que d'autres constructeurs sont destinés à être utilisés comme constructeurs "intermédiaires" par classes descendantes (ces constructeurs n'appellent pas AddToChildren).

Une telle conception peut être considérée comme très discutable et sujette aux erreurs. Notez, par exemple, que C::C appels AddToChildren, qui est censé être à B::childrenthis ajoutent (il était l'intention?), Et invoque également le constructeur B::B, qui ajoutera également this à B::children. Ainsi, la même valeur this est ajoutée deux fois à la liste. Cela ne semble pas avoir de sens.

Vous devez déterminer ce que vous essayez de faire, puis corriger votre conception. Une fois que vous avez terminé, vous pouvez "virtualiser" la liste en utilisant la technique proposée par Neil (introduction d'une méthode virtuelle GetList). Neil a plus tard écrit à tort que ça ne marcherait pas. En fait, cela fonctionnera parfaitement bien (encore une fois, en supposant que je comprends correctement votre conception).


(Compte tenu les commentaires éclaircissant OP)

Alors, vous voulez des objets B à ajouter à la liste A::children et C objets à ajouter aux deux listes A::children et B::children. Ceci peut être réalisé par

class A { 
    ... 
    int a; 
    static vector<A*> children; 
    ... 
    A(int a) : a(a) {} 

    virtual vector<A*> *GetList() = 0; 

    void AddToChildren(A* obj) { // note: non-virtual 
    GetList()->push_back(obj); 
    } 
    ... 
}; 

class B : public A { 
    ... 
    int b; 
    static vector<A*> children; 
    ... 
    B(int a, int b) : b(b), A(a) { 
    AddToChildren(this); 
    } 

    virtual vector<A*> *GetList() { 
    return &A::children; 
    } 
    ... 
}; 

class C : public B { 
    ... 
    int c; 
    ... 
    C(int a, int b, int c) : c(c), B(a,b) { 
    AddToChildren(this); 
    };  

    virtual vector<A*> *GetList() { 
    return &B::children; 
    } 
    ... 
}; 

Notez que, malgré ce qui a été dit par d'autres affiches, appels virtuels ne fonctionnent ici et ils fonctionnent exactement comme nous avons besoin de travailler pour obtenir la fonctionnalité demandée. Notez cependant que dans ce cas, il n'y a aucun intérêt à rendre la méthode AddToChildren virtuelle, la virtualité de GetList seule est suffisante.

En outre, le tout fait peu si AddToChildren fait juste un push_back. Il n'y a pas beaucoup de sens la construction d'une telle infrastructure pour un "mince" AddToChildren seul. Faites simplement ce que vous voulez faire explicitement dans chaque constructeur.

+0

Hmm, cela peut sembler un peu flou. C n'a pas de liste d'enfants car c'est une "feuille", c'est la classe du bas. L'intention était que chaque classe descendante s'ajoute aux listes "Enfants" de toutes les classes dont elle hérite. Donc C devrait être ajouté à A :: children et B :: children. Dans la question il y a une version mise à jour, j'ai fait travailler mon design avec la proposition de Neil. Je pense que tu as raison à propos de ma conception de classe. Le changer a rendu la chose un peu plus simple (mais pas encore le meilleur je pense). – mcmil

4

Modifier: comme le souligne ironique, cela ne s'applique pas dans le cas où vous avez posté. Je le laisse non supprimé car il peut être utile dans d'autres situations. Lorsque vous appelez AddToChildren(), vous obtenez l'implémentation de A, qui (bien sûr) ajoute au membre statique de A.

Cela est dû au fait que C++ n'a pas de concept de "données virtuelles". Un moyen de contourner ce serait d'ajouter une fonction virtuelle appelée GetList(). IA, il ressemble à ceci (code non testé):

virtual vector <a*> * GetList() { 
    return & A::children; 
} 

et B:

virtual vector <a*> * GetList() { 
    return & B::children; 
} 

changer ensuite AddToChildren à:

void A::AddToChildren(A *obj) 
{ 
    GetList()->push_back(obj); 
} 
+1

@Neil: Je ne sais pas pourquoi vous dénigriez votre propre réponse. La remarque d'Ironic est complètement incorrecte. L'OP veut que les pointeurs soient ajoutés à 'B :: children' quand' B' est construit. C'est exactement comme cela fonctionnera dans votre implémentation. Les appels virtuels faits à partir de construtor * sont * virtuels en C++ (contrairement à d'étranges croyances populaires mais incorect). Ils travaillent simplement «jusqu'au niveau» de la classe dont le constructeur travaille actuellement. L'OP appelle 'AddToChildren' de' B :: B'. Dans ce cas, l'appel virtuel sera résolu en 'B :: GetList', comme prévu. – AnT

+1

@Andrey: les fonctions virtuelles dans les constructeurs n'agissent pas comme si elles étaient virtuelles. Pour cette raison, nous disons qu'ils sont brisés. Il s'avère que "jusqu'au niveau" a exactement la même sémantique que "traité comme si la fonction n'était pas virtuelle". –

+0

@Caspin: Incorrect. Vous semblez être victime de la même croyance populaire, mais incorrecte. Les fonctions virtuelles appelées par les constructeurs (directement ou indirectement) * agissent comme virtuelles. Sauf que la "profondeur" de leur "virtualité" est limitée par le type de l'objet dont le constructeur est actuellement actif. Cette "quantité de virtualité", bien que limitée, est parfaitement suffisante pour mettre en œuvre la fonctionnalité demandée par le PO. – AnT

3

Le code d'une fonction applique uniquement à la classe il est défini dans, même si c'est virtuel. Ainsi, la fonction suivante toujours appliquée à la classe A et ajoute donc A::children:

void A::AddToChildren(A *obj) 
{ 
    children.push_back(obj); 
} 

Si vous voulez des vecteurs distincts pour chaque classe, vous avez pas d'autre choix que de répéter le code d'une classe à une autre (la variable statique, son initialisation, et soit une fonction add-to-children, soit une fonction get-children-list). Je déconseillerais d'appeler des fonctions virtuelles dans les constructeurs, cependant.

Une autre approche que vous pourriez être intéressé par serait d'avoir un modèle de classe distincte pour un tel stockage:

template<typename T> struct children 
{ 
    static std::vector<T*> list; 
    static void add(T* t) { list.push_back(t); } 
}; 

B::B() : A() { 
    children<A>::add(this); 
} 

C::C() : B() { 
    children<B>::add(this); 
} 
+0

Je vois. La solution de gabarit est belle, mais comme c'est un projet à l'université, je préfère garder la solution au même endroit. – mcmil

+0

Vous pouvez toujours faire du modèle struct un membre de A. –

-1

@Neli: Une telle approche ne résoudrait pas ce problème concret car en C++ appels à des fonctions virtuelles à partir Destructeurs/les constructeurs ne sont pas virtuels. Ainsi, à partir du constructeur GetList() retournera toujours les enfants de A.

P.s. ceci devrait être dans le commentaire à la réponse, mais je ne pourrais pas trouver l'option appropriée.

P.P.S. Spécialement pour AndreyT avec amour

Veuillez lire attentivement ce qui suit. Il est de norme internationale C++, 12.7. Lorsqu'une fonction virtuelle est appelée directement ou indirectement d'un constructeur (y compris de l'initialisateur mem pour un membre de données) ou d'un destructeur, et que l'objet auquel s'applique l'appel est l'objet en cours de construction ou de destruction, le La fonction appelée est celle définie dans la classe propre du constructeur ou du destructeur ou dans l'une de ses bases, mais pas une fonction qui la remplace dans une classe dérivée de la classe du constructeur ou du destructeur, ou la surpasse dans l'une des autres classes de base. objet dérivé (1.8). Si l'appel de fonction virtuelle utilise un accès de membre de classe explicite (5.2.5) et que l'expression d'objet fait référence à l'objet en cours de construction ou de destruction, son type n'est ni la classe propre du constructeur ou du destructeur ni le résultat du l'appel est indéfini.

+0

Whups, j'ai manqué qu'il appelait le code du ctor/dtor - vous avez raison. –

+0

En C++, les appels aux fonctions virtuelles des constructeurs/destructeurs sont aussi virtuels que n'importe où ailleurs. La seule différence est que le type dynamique de l'objet est défini comme étant le type, dont le constrcutor travaille actuellement. Si votre compilateur traduit ces appels comme non-virtuel (cela a du sens), ce n'est rien qu'une simple optimisation spécifique au compilateur, qui n'a aucun rapport avec le langage C++ en général. – AnT

+1

Dans l'approche de Neil, tout fonctionnera comme prévu: Quand 'B' est construit,' A :: AddToChildren' va appeler 'B :: GetList', parce que l'appel * est * virtuel. – AnT

0

Vous ne nous avez pas montré la mise en œuvre de A::AddToChildren ou B::AddToChildren, mais c'est là que le problème sera. D'une manière ou d'une autre, votre implémentation de B::AddToChildren ajoute au mauvais vecteur. Peut-être que vous n'avez pas spécialisé la méthode pour B? Ne peut pas vraiment dire sans voir cette partie du code.

Modifier après les commentaires: Si vous insistez sur l'utilisation de l'héritage ici, vous pouvez faire quelque chose comme:

class A 
{ 
    ... 
    virtual vector<A*> * ChildrenPtr(); 
}; 

... 

A::ChildrenPtr() {return &A::children;} 
B::ChildrenPtr() {return &B::children;} 
C::ChildrenPtr() {return NULL;} 

void A::AddToChildren(A *obj) 
{ 
    vector<A*> * pChildren = ChildrenPtr(); 
    if (pChildren) 
     pChildren->push_back(obj); 
} 

Dans ce cas, je pense franchement c'est plus déroutant, mais, pas moins. Ceci est très similaire à ce que Neil Butterworth a dit plus haut, mais a aussi un peu de sécurité pour quiconque invoque C :: AddToChildren (A * obj).

+0

Je voulais éviter de copier le A :: AddToChildren à B :: AddToChildren. Cela a résolu le problème, mais je cherche une solution plus ... élégante. Donc le B :: AddToChildren est hérité de A :: AddToChildren. – mcmil

+0

Le code peut être identique textuellement, mais ils accèdent à différents vecteurs statiques, même si les deux vecteurs ont le même nom (donner ou prendre la classe de qualification). La duplication est le seul moyen de dupliquer le code: les noms peuvent être identiques, mais les variables (le vecteur) ne le sont pas. La création de modèles évite la duplication du texte, mais génère toujours le même code. –

Questions connexes