Quelqu'un l'a mentionné dans l'IRC comme étant le problème de découpage.
c++
inheritance
c++-faq
object-slicing
Frankomania
la source
la source
A a = b;
a
est maintenant un objet de typeA
qui a une copie deB::foo
. Ce sera une erreur de le rejeter maintenant, je pense.B b1; B b2; A& b2_ref = b2; b2 = b1
. Vous pourriez penser que vous avez copiéb1
àb2
, mais vous n'avez pas! Vous avez copié une partie deb1
àb2
(la partieb1
qui aB
hérité deA
), et laissé les autres partiesb2
inchangées.b2
est maintenant une créature frankensteinienne composée de quelques morceaux deb1
suivi de quelques morceaux deb2
. Pouah! Downvoting parce que je pense que la réponse est très trompeuse.B b1; B b2; A& b2_ref = b2; b2_ref = b1
" Le vrai problème se produit si vous " ... dérivez d'une classe avec un opérateur d'affectation non virtuel. EstA
même destiné à la dérivation? Il n'a pas de fonctions virtuelles. Si vous dérivez d'un type, vous devez faire face au fait que ses fonctions membres peuvent être appelées!La plupart des réponses ici ne parviennent pas à expliquer quel est le problème réel avec le découpage. Ils n'expliquent que les cas bénins de tranchage, pas les cas perfides. Supposons, comme les autres réponses, que vous traitez avec deux classes
A
etB
, d'oùB
dérive (publiquement)A
.Dans cette situation, C ++ vous permet de passer une instance de
B
àA
l'opérateur d'affectation de »(et aussi au constructeur de copie). Cela fonctionne car une instance deB
peut être convertie en aconst A&
, ce que les opérateurs d'affectation et les constructeurs de copies attendent de leurs arguments.Le cas bénin
Il ne se passe rien de mal là-bas - vous avez demandé une instance de
A
ce qui est une copie deB
, et c'est exactement ce que vous obtenez. Bien sûr,a
ne contiendra pas certains deb
ses membres, mais comment cela devrait-il? C'est unA
, après tout, pas unB
, donc il n'a même pas entendu parler de ces membres, et encore moins serait en mesure de les stocker.Le cas perfide
Vous pourriez penser que ce
b2
sera une copie de lab1
suite. Mais, hélas, ce n'est pas ! Si vous l'inspectez, vous découvrirez qu'ilb2
s'agit d'une créature frankensteinienne, faite de quelques morceaux deb1
(les morceaux quiB
héritent deA
) et de quelques morceaux deb2
(les morceaux qui neB
contiennent que). Aie!Qu'est-il arrivé? Eh bien, C ++ par défaut ne traite pas les opérateurs d'affectation comme
virtual
. Ainsi, la lignea_ref = b1
appellera l'opérateur d'affectation deA
, pas celui deB
. En effet, pour les fonctions non virtuelles, le type déclaré (formellement: statique ) (qui estA&
) détermine quelle fonction est appelée, par opposition au type réel (formellement: dynamique ) (qui le seraitB
, cara_ref
référence une instance deB
) . Maintenant,A
l'opérateur d'affectation de ne connaît évidemment que les membres déclarés dansA
, donc il ne copiera que ceux-ci, laissant les membres ajoutés dansB
inchangés.Une solution
Affecter uniquement à des parties d'un objet n'a généralement pas de sens, mais C ++, malheureusement, ne fournit aucun moyen intégré pour l'interdire. Vous pouvez cependant lancer le vôtre. La première étape consiste à rendre l'opérateur d'affectation virtuel . Cela garantira que c'est toujours l' opérateur d'affectation du type réel qui est appelé, pas celui du type déclaré . La deuxième étape consiste à utiliser
dynamic_cast
pour vérifier que l'objet affecté a un type compatible. La troisième étape consiste à faire l'affectation réelle dans un (protégé!) Membreassign()
, depuisB
« sassign()
voudront probablement utiliserA
» sassign()
pour copierA
de membres.Notez que, pour plus de commodité,
B
soperator=
remplace de manière covariante le type de retour, car il sait qu'il retourne une instance deB
.la source
derived
valeur peut être donnée au code attendant unebase
valeur, soit toute référence dérivée peut être utilisée comme référence de base. J'aimerais voir un langage avec un système de type qui aborde les deux concepts séparément. Il existe de nombreux cas où une référence dérivée doit être substituable à une référence de base, mais les instances dérivées ne doivent pas être substituables à celles de base; il existe également de nombreux cas où les instances doivent être convertibles mais les références ne doivent pas se substituer.Si vous avez une classe de base
A
et une classe dérivéeB
, vous pouvez effectuer les opérations suivantes.Maintenant, la méthode a
wantAnA
besoin d'une copie dederived
. Cependant, l'objetderived
ne peut pas être copié complètement, car la classeB
pourrait inventer des variables membres supplémentaires qui ne sont pas dans sa classe de baseA
.Par conséquent, pour appeler
wantAnA
, le compilateur "découpera" tous les membres supplémentaires de la classe dérivée. Le résultat peut être un objet que vous ne souhaitez pas créer, carA
-objet (tout comportement spécial de la classeB
est perdu).la source
wantAnA
(comme son nom l'indique!) Veut unA
, alors c'est ce qu'il obtient. Et une instance deA
, se comportera, euh, comme unA
. Comment est-ce surprenant?derived
le typeA
. Le cast implicite est toujours une source de comportement inattendu en C ++, car il est souvent difficile de comprendre en regardant le code localement qu'un cast a eu lieu.Ce sont toutes de bonnes réponses. Je voudrais juste ajouter un exemple d'exécution lors du passage d'objets par valeur vs par référence:
La sortie est:
la source
Le troisième match dans google pour "C ++ slicing" me donne cet article Wikipedia http://en.wikipedia.org/wiki/Object_slicing et ce (chauffé, mais les premiers messages définissent le problème): http://bytes.com/ forum / thread163565.html
C'est donc lorsque vous affectez un objet d'une sous-classe à la super classe. La superclasse ne sait rien des informations supplémentaires contenues dans la sous-classe et n'a pas de place pour les stocker, de sorte que les informations supplémentaires sont "découpées".
Si ces liens ne donnent pas suffisamment d'informations pour une "bonne réponse", veuillez modifier votre question pour nous faire savoir ce que vous recherchez de plus.
la source
Le problème de découpage est grave car il peut entraîner une corruption de la mémoire et il est très difficile de garantir qu'un programme n'en souffre pas. Pour le concevoir hors du langage, les classes qui prennent en charge l'héritage doivent être accessibles par référence uniquement (et non par valeur). Le langage de programmation D possède cette propriété.
Considérez la classe A et la classe B dérivées de A. La corruption de mémoire peut se produire si la partie A a un pointeur p et une instance B qui pointe p vers les données supplémentaires de B. Ensuite, lorsque les données supplémentaires sont coupées, p pointe vers les ordures.
la source
Derived
sont implicitement convertibles enBase
.)En C ++, un objet de classe dérivé peut être affecté à un objet de classe de base, mais l'inverse n'est pas possible.
Le découpage d'objet se produit lorsqu'un objet de classe dérivée est affecté à un objet de classe de base, les attributs supplémentaires d'un objet de classe dérivée sont découpés pour former l'objet de classe de base.
la source
Le problème de découpage en C ++ provient de la sémantique de la valeur de ses objets, qui est restée principalement due à la compatibilité avec les structures C. Vous devez utiliser une référence explicite ou une syntaxe de pointeur pour obtenir un comportement d'objet "normal" trouvé dans la plupart des autres langages qui font des objets, c'est-à-dire que les objets sont toujours transmis par référence.
La réponse courte est que vous découpez l'objet en affectant un objet dérivé à un objet de base par valeur , c'est-à-dire que l'objet restant n'est qu'une partie de l'objet dérivé. Afin de préserver la sémantique des valeurs, le découpage est un comportement raisonnable et a ses utilisations relativement rares, qui n'existent pas dans la plupart des autres langues. Certaines personnes le considèrent comme une caractéristique du C ++, tandis que beaucoup le considèrent comme l'une des bizarreries / défauts du C ++.
la source
struct
, la compatibilité ou tout autre non-sens que tout prêtre OOP aléatoire vous a dit.Base
doit prendre exactement dessizeof(Base)
octets en mémoire, avec un alignement possible, peut-être, c'est pourquoi "assignation" (copie sur pile ) ne copiera pas les membres de classe dérivés, leurs décalages sont en dehors de sizeof. Pour éviter de "perdre des données", utilisez simplement le pointeur, comme n'importe qui d'autre, car la mémoire du pointeur est fixée en place et en taille, tandis que la pile est très volatileAlors ... Pourquoi la perte des informations dérivées est-elle mauvaise? ... car l'auteur de la classe dérivée peut avoir modifié la représentation de telle sorte que la suppression des informations supplémentaires modifie la valeur représentée par l'objet. Cela peut se produire si la classe dérivée est utilisée pour mettre en cache une représentation plus efficace pour certaines opérations, mais coûteuse à reconvertir en représentation de base.
Vous avez également pensé que quelqu'un devrait également mentionner ce que vous devez faire pour éviter de trancher ... Obtenez une copie des normes de codage C ++, des lignes directrices de 101 règles et des meilleures pratiques. Faire face au tranchage est # 54.
Il suggère un modèle quelque peu sophistiqué pour traiter entièrement le problème: avoir un constructeur de copie protégé, un DoClone virtuel pur protégé et un clone public avec une assertion qui vous dira si une classe dérivée (supplémentaire) n'a pas implémenté correctement DoClone. (La méthode Clone crée une copie complète appropriée de l'objet polymorphe.)
Vous pouvez également marquer le constructeur de copie sur la base explicite, ce qui permet un découpage explicite si vous le souhaitez.
la source
1. LA DÉFINITION DU PROBLÈME DE TRANCHAGE
Si D est une classe dérivée de la classe de base B, vous pouvez affecter un objet de type Derived à une variable (ou paramètre) de type Base.
EXEMPLE
Bien que l'affectation ci-dessus soit autorisée, la valeur affectée à l'animal variable perd son champ de race. C'est ce qu'on appelle le problème de tranchage .
2. COMMENT RÉSOUDRE LE PROBLÈME DE TRANCHAGE
Pour vaincre le problème, nous utilisons des pointeurs vers des variables dynamiques.
EXEMPLE
Dans ce cas, aucun des membres de données ou des fonctions membres de la variable dynamique pointée par ptrD (objet de classe descendant) ne sera perdu. De plus, si vous devez utiliser des fonctions, la fonction doit être une fonction virtuelle.
la source
dog
ne faisant pas partie de la classePet
(lebreed
membre de données) ne soit pas copié dans la variablepet
? Le code ne s'intéresse qu'auxPet
membres des données - apparemment. Le tranchage est définitivement un "problème" s'il n'est pas souhaité, mais je ne le vois pas ici.((Dog *)ptrP)
" Je suggère d'utiliserstatic_cast<Dog*>(ptrP)
Dog::breed
) n'est en aucun cas une ERREUR liée à la COUPE?Il me semble que le découpage n'est pas tant un problème que lorsque vos propres classes et programmes sont mal architecturés / conçus.
Si je passe un objet de sous-classe en tant que paramètre à une méthode, qui prend un paramètre de type superclasse, je devrais certainement en être conscient et connaître en interne, la méthode appelée fonctionnera uniquement avec l'objet superclasse (aka baseclass).
Il me semble que seule l'attente déraisonnable selon laquelle la fourniture d'une sous-classe dans laquelle une classe de base est demandée, aboutirait d'une manière ou d'une autre à des résultats spécifiques à la sous-classe, entraînerait un problème de découpage. C'est soit une mauvaise conception dans l'utilisation de la méthode, soit une mauvaise mise en œuvre de la sous-classe. Je suppose que c'est généralement le résultat de sacrifier une bonne conception de POO au profit d'opportunités ou de gains de performances.
la source
OK, je vais essayer après avoir lu de nombreux articles expliquant le découpage d'objets, mais pas comment cela devient problématique.
Le scénario vicieux qui peut entraîner une corruption de la mémoire est le suivant:
la source
Le découpage signifie que les données ajoutées par une sous-classe sont rejetées lorsqu'un objet de la sous-classe est transmis ou renvoyé par valeur ou à partir d'une fonction attend un objet de classe de base.
Explication: tenez compte de la déclaration de classe suivante:
Comme les fonctions de copie de classe de base ne savent rien du dérivé, seule la partie de base du dérivé est copiée. Ceci est communément appelé découpage.
la source
la source
lorsqu'un objet de classe dérivée est affecté à un objet de classe de base, des attributs supplémentaires d'un objet de classe dérivée sont coupés (supprimés) de l'objet de classe de base.
la source
Lorsqu'un objet de classe dérivée est affecté à un objet de classe de base, tous les membres de l'objet de classe dérivée sont copiés dans l'objet de classe de base, à l'exception des membres qui ne sont pas présents dans la classe de base. Ces membres sont découpés par le compilateur. C'est ce qu'on appelle le découpage d'objets.
Voici un exemple:
Il générera:
la source
J'ai juste traversé le problème du tranchage et j'ai rapidement atterri ici. Permettez-moi donc d'ajouter mes deux cents à cela.
Prenons un exemple du "code de production" (ou quelque chose qui se rapproche un peu):
Disons que nous avons quelque chose qui envoie des actions. Une interface utilisateur de centre de contrôle par exemple.
Cette interface utilisateur doit obtenir une liste des éléments qui peuvent actuellement être envoyés. Nous définissons donc une classe qui contient les informations de répartition. Appelons ça
Action
. Donc,Action
a quelques variables membres. Pour plus de simplicité, nous avons juste 2, étant unstd::string name
et unstd::function<void()> f
. Ensuite, il a unvoid activate()
qui exécute simplement lef
membre.L'interface utilisateur est donc
std::vector<Action>
fournie. Imaginez quelques fonctions comme:Nous avons maintenant établi à quoi cela ressemble du point de vue de l'interface utilisateur. Aucun problème jusqu'ici. Mais un autre gars qui travaille sur ce projet décide soudainement qu'il existe des actions spécialisées qui nécessitent plus d'informations dans l'
Action
objet. Pour quelle raison jamais. Cela pourrait également être résolu avec des captures lambda. Cet exemple n'est pas pris 1-1 du code.Donc, le gars dérive
Action
pour ajouter sa propre saveur.Il passe une instance de sa classe brassée à la maison au
push_back
mais ensuite le programme se détraque.Alors, qu'est-ce-qu'il s'est passé?
Comme vous l'avez peut- être deviné: l'objet a été découpé.
Les informations supplémentaires de l'instance ont été perdues et
f
sont désormais sujettes à un comportement non défini.J'espère que cet exemple apportera de la lumière aux personnes qui ne peuvent pas vraiment imaginer des choses en parlant de
A
s etB
s dérivant d'une manière ou d'une autre.la source