2010-02-11 5 views
5

J'ai le code comme ceci:C++ Comment se fait-il que je ne puisse pas assigner une classe de base à une classe enfant?

class Base 
{ 
public: 
    void operator = (const Base& base_) 
    { 
    } 
}; 

class Child : public Base 
{ 
public: 

}; 

void func() 
{ 
    const Base base; 
    Child child; 
    child = base; 
} 

Ma question est la suivante: depuis l'enfance dérive de la base (d'où il devrait hériter de l'opérateur de base =), comment se fait lorsque la déclaration

child = base; 

est exécuté, je obtenir une erreur de compilation comme ceci:

>.\main.cpp(78) : error C2679: binary '=' : no operator found which takes a right-hand operand of type 'const Base' (or there is no acceptable conversion) 
1>  .\main.cpp(69): could be 'Child &Child::operator =(const Child &)' 
1>  while trying to match the argument list '(Child, const Base)' 

le comportement que je veux est pour la classe de l'enfant à reconnaître qu'il est affecté d'être une classe de base, et juste appelle "automatiquement" l'opérateur de son parent =.

Une fois que j'ajouté ce code à la classe des enfants

void operator = (const Base& base_) 
{ 
    Base::operator=(base_); 
} 

puis de tout compilé bien. Bien que je ne pense pas que ce serait bon parce que si j'ai comme 5 classes différentes qui héritent de la base, alors je dois répéter le même code dans chaque classe dérivée.

NOTE: Mon intention de copier le Base-Child est de copier simplement les membres qui sont communs aux deux Base et Child (ce qui serait tous les membres de Base). Même après avoir lu toutes les réponses ci-dessous, je ne vois vraiment pas pourquoi C++ ne le permet pas, surtout s'il y a un operator= explicite défini dans la classe Base.

+4

Vous semblez manquer les bases du polymorphisme. –

+5

Un chat est un animal mais un animal n'est pas nécessairement un chat. – jason

+0

Mon intention était simplement de copier les membres qui sont communs entre les classes Base et Enfant. – sivabudh

Répondre

12

La norme fournit la raison de votre question dans 12,8/10 « objets de classe Copie » (soulignement ajouté):

Parce qu'un opérateur d'affectation de copie est implicitement déclarée pour une classe si non déclarée par l'utilisateur , un opérateur d'affectation de copie de classe de base est toujours masqué par l'opérateur d'affectation de copie d'une classe dérivée (13.5.3).

Donc, puisque il y a un implicitement déclaré operator=(const Child&) lorsque le compilateur effectue la recherche de nom/résolution de surcharge pour un Child::operator=(), la signature de la fonction de la classe Base est jamais considéré (il est caché).

+1

Oui et comme je l'ai noté dans mon article, vous pouvez utiliser "using" pour ramener cet opérateur = dans la portée de Childs. (Ce que je trouve effrayant) –

+1

Je me sens comme caché est pauvre formulation pour la norme. void Base :: operator = (const Base & base_); et void Chili :: operator = (const Base & base_); sont intrinsèquement différents car on assigne une Base à une Base et l'autre à un Enfant. Semble idiot de dire son caché quand ils sont différentes fonctions. Il doit être masqué car il n'est pas logique que cette fonction existe dans la classe Child. – 0xC0DEFACE

+0

Le nom caché pour 'operator =()' est traité comme pour tout autre nom de fonction qui pourrait être caché avec une différence majeure: si vous ne déclarez pas 'operator =()' pour une classe, vous obtenez une déclaration implicite le nom caché est là que vous le vouliez ou non et si vous auriez aimé que le nom ne soit pas caché, vous ne pouvez pas faire grand-chose sauf pour définir un 'operator =()' dans la classe dérivée qui redirige vers la base implémentation de classe. C'est pourquoi la norme appelle le comportement de dissimulation de nom spécifiquement pour 'operator =()'. –

0

Le problème est que vous ne pouvez pas affecter une valeur constante à une variable non constante. Si vous le pouvez, vous pouvez potentiellement modifier la valeur constante en modifiant la variable non constante.

Est-il nécessaire que la base soit const? Je pose la question parce que vous êtes vraiment confronté à deux problèmes: la constabilité et l'hérédité/polymorphisme.

EDIT: Je suis en train de me deviner maintenant concernant ma première déclaration. Toute clarification?

+0

Je pensais que d'abord, mais l'affectation est à l'enfant qui n'est pas const, pas la base (qui est const). –

+0

Droit ... n'est-ce pas le problème? Que se passera-t-il lorsqu'une méthode non-const est appelée sur l'enfant alors? –

+0

_child_ n'est pas const, donc appeler une fonction non-const est OK. _base_ est const, mais operator = prend un argument _const Base & _, ce qui est également OK. –

2

Parce qu'une base n'est pas un enfant. Considérez:

class Child : public Base 
{ 
public: 
    SomeFunc(); 
}; 

void func() 
{ 
    const Base base; 
    Child child; 
    child = base; 
    child.SomeFunc();  // whatcha gonna call? 
} 

MISE À JOUR: Pour répondre à la question dans le commentaire, child.SomeFunc(); est parfaitement légal - c'est un problème que si e valeur de child n'est pas vraiment un objet enfant. Le compilateur ne peut pas autoriser child = base; car cela créerait une situation dans laquelle l'appel légal échoue.

+0

C'est un bon point, mais je pense qu'il demande pourquoi il obtient l'erreur du compilateur. –

1

Lorsque vous ne déclarez pas d'affectation de copie explicitement, le compilateur C++ créera l'affectation de copie pour vous. Dans votre cas, ce serait

class Child : public Base 
{ 
public: 
Child& operator=(const Child& rhs) { ... } 

}; 

Et c'est pourquoi vous obtenez cette erreur de compilation. En outre, il est plus logique de renvoyer une référence à lui-même lorsque vous fournissez une attribution de copie, car cela vous permettra de chaîner l'opérateur ensemble.

Ce n'est pas une bonne idée de déclarer une affectation de copie comme celle-ci.Je ne vois pas pourquoi vous voulez seulement copier la partie de classe de base quand vous faites une assignation de copie.

+0

-1 Cela n'a rien à voir avec l'erreur. –

+0

bien, l'erreur de compilation le dit clairement. Je n'ai pas votre commentaire. Il demande pourquoi il obtient l'erreur de compilation. –

+1

La surcharge de l'opérateur d'affectation, tout en étant la raison pour laquelle le compilateur signale, est totalement orthogonale au problème réel ici, à savoir que l'affiche n'a pas compris l'idée du polymorphisme. Votre réponse est comme dire aux gens d'utiliser 'const_cast' pour contourner la const-correction. Cela pourrait résoudre l'erreur, mais ce n'est pas le vrai problème. –

6

Vous ne pouvez pas affecter Base à l'enfant, car l'option Enfant peut ne pas être la base. Pour utiliser l'exemple typique des animaux, ce que vous avez ici est:

const Animal animal; 
Dog dog; 
dog = animal; 

Mais animal pourrait être un chat, et vous ne peut certainement pas affecter un chat à un chien.

Cela dit, on ne pouvait pas le faire dans l'autre sens, soit:

const Dog dog; 
Animal animal; 
animal = dog; 

Un chien est certainement un animal, mais vous avez un problème de taille ici. animal prend sizeof(Animal) espace sur la pile, mais dog prend sizeof(Dog) espace, et ces deux montants peuvent être différents. Lorsque vous travaillez avec le polymorphisme en C++, vous devez travailler avec des références ou des pointeurs. Par exemple, ce qui suit est bien:

Dog* const dog; 
Animal* animal; 
animal = dog; 

Ici, la taille d'un pointeur est le même quel que soit ce qu'il fait, donc la mission est possible. Notez que vous devez toujours gérer les conversions de hiérarchie correctes. Même lorsque vous utilisez un pointeur, vous ne pouvez pas affecter une Base à un Enfant, à cause de la raison que j'ai déjà expliquée: Une Base peut être quelque chose de complètement différent de l'Enfant.

+0

@Poita_: merci pour une bonne réponse. Cependant, j'ai une question. Quand on fait chien = animal, comment se fait-il qu'il ne peut copier que la partie Animal au chien? Est-ce que tu vois ce que je veux dire? par exemple. disons que la classe Animal a un membre privé appelé int numLegs; Ensuite, quand nous faisons dog = animal, C++ devrait juste copier Animal :: numLegs et l'assigner à Dog :: numLegs. – sivabudh

+0

En d'autres termes, il copie les parties communes entre les deux classes. – sivabudh

+2

Je vois ce que tu veux dire, mais qu'arriverait-il aux parties du chien qui ne sont pas spécifiées par l'Animal? Vous pourriez concevoir un langage pour fonctionner de cette façon, mais le problème fondamental est que cela n'a aucun sens. –

1

La réponse est plus effrayante que je ne le pensais. Vous pouvez le faire. Le standard evens a une petite note à ce sujet dans la section 7.3.3 Le problème principal est que l'opérateur = qui est défini par défaut dans Child cache l'opérateur = à partir de la base, de sorte que vous devez l'importer dans la classe en utilisant "using ".

Cela compile OK pour moi. Cependant, je trouve cette idée très effrayante, et potentiellement lourde de comportement indéfini si des choses dans l'enfant doivent être initialisées.

class Base 
{ 
public: 
    void operator = (const Base& base_) 
    { 
    } 
}; 

class Child : public Base 
{ 
public: 
using Base::operator=; 

}; 

int main() 
{ 
    const Base base = Base(); 
    Child child; 
    child = base; 
} 

EDIT: Fait à nouveau la base const et évite l'erreur «const non initialisée».

6

Le code ci-dessous est le comportement que je voulais depuis le début, et il compile,

class Base 
{ 
public: 
    void operator = (const Base& base_) 
    { 
    } 
}; 

class Child : public Base 
{ 
}; 

void func() 
{ 
    const Base base; 

    Child child; 

    child.Base::operator=(base); 
} 

Je ne savais pas que vous pouvez appeler explicitement quelque chose comme:

child.Base::operator=(base); 

Quoi qu'il en soit, j'ai appris lot. Merci à tous ceux qui ont posté des réponses ici.

+0

Note: J'ai appris la syntaxe ci-dessus en lisant http://www.gotw.ca/publications/mill08.htm de Sutter (motivé par le commentaire de @Michael Burr) – sivabudh

2

Ce n'est pas vraiment une réponse, mais la question pratique a été répondue. C'est le pourquoi-non.

Les cours devraient signifier quelque chose. Il devrait y avoir des choses utiles que vous pouvez dire sur n'importe quel objet bien formé dans la classe ("invariants de classe"), parce que de cette façon vous pouvez raisonner sur les programmes en fonction du fonctionnement d'une classe. Si une classe est juste une collection de membres de données, vous ne pouvez pas faire cela, et devez raisonner sur le programme sur la base de chaque élément de données individuel.

L'opérateur d'affectation proposé changera tous les membres de données de la classe de base, mais ne touchera pas ceux définis dans la classe enfant. Cela signifie une de deux choses.

Si un objet enfant est bien formé après qu'un autre objet Base a été déposé dans son composant de base, cela signifie que les membres de données supplémentaires n'ont aucune connexion réelle avec les membres de données de base. Dans ce cas, l'enfant n'est pas vraiment un sous-type de base, et l'héritage public est la mauvaise relation. L'enfant n'est pas dans une relation «est-a» avec la base, mais plutôt dans une relation «has-a». Ceci est généralement exprimé par la composition (enfant a un membre de données de base), et peut également être exprimé par héritage privé.

Si un objet enfant n'est pas correctement formé après qu'un autre objet de base a été déposé dans son composant de base, l'opérateur d'affectation proposé est une méthode très rapide et pratique pour modifier une variable. Par exemple, prenons une classe de base Mammal et deux classes enfant, Cetacean et Bat. Animal a des choses comme la taille et le poids, et les classes d'enfants ont des champs liés à ce qu'ils mangent et comment ils se déplacent. Nous assignons un cétacé à un mammifère, ce qui est raisonnable (tous les champs spécifiques aux cétacés sont juste abandonnés), et maintenant nous assignons ce mammifère à une chauve-souris. Soudainement, nous avons une chauve-souris de vingt tonnes. Avec un ensemble de classes bien conçu, la seule façon dont je puisse concevoir que cet opérateur d'assignation soit envisagé est si les classes enfants n'ont pas de membres de données supplémentaires, seulement un comportement supplémentaire, et même alors c'est suspect.

Par conséquent, mon seul conseil est de refaire votre conception de classe. Beaucoup de gens, en particulier ceux qui sont familiers avec Java et d'autres langages qui tendent vers des hiérarchies d'héritage profondes, utilisent beaucoup trop l'héritage en C++. C++ fonctionne généralement mieux avec un grand nombre de classes de base et une hiérarchie relativement superficielle.

Questions connexes