Qu'est-ce que le découpage d'objets?

Réponses:

610

Le "découpage" est l'endroit où vous affectez un objet d'une classe dérivée à une instance d'une classe de base, perdant ainsi une partie des informations - une partie est "découpée".

Par exemple,

class A {
   int foo;
};

class B : public A {
   int bar;
};

Un objet de type Ba donc deux membres de données, fooet bar.

Alors si vous deviez écrire ceci:

B b;

A a = b;

Les informations bsur le membre barsont ensuite perdues a.

David Dibben
la source
66
Très informatif, mais voir stackoverflow.com/questions/274626#274636 pour un exemple de la façon dont le découpage se produit pendant les appels de méthode (ce qui souligne un peu mieux le danger que l'exemple d'affectation simple).
Blair Conrad
55
Intéressant. Je programme en C ++ depuis 15 ans et ce problème ne m'est jamais venu à l'esprit, car j'ai toujours passé des objets par référence pour des raisons d'efficacité et de style personnel. Va montrer comment de bonnes habitudes peuvent vous aider.
Karl Bielefeldt
10
@Felix Merci mais je ne pense pas que le retour en arrière (car pas une arithmétique de pointeur) fonctionnera, A a = b; aest maintenant un objet de type Aqui a une copie de B::foo. Ce sera une erreur de le rejeter maintenant, je pense.
37
Ce n'est pas du "tranchage", ou du moins une variante bénigne de celui-ci. Le vrai problème se produit si vous le faites 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 de b1à b2(la partie b1qui a Bhérité de A), et laissé les autres parties b2inchangées. b2est maintenant une créature frankensteinienne composée de quelques morceaux de b1suivi de quelques morceaux de b2. Pouah! Downvoting parce que je pense que la réponse est très trompeuse.
fgp
24
@fgp Votre commentaire doit se lire 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. Est Amê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!
curiousguy
510

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 Aet B, d'où Bdérive (publiquement) A.

Dans cette situation, C ++ vous permet de passer une instance de Bà Al'opérateur d'affectation de »(et aussi au constructeur de copie). Cela fonctionne car une instance de Bpeut être convertie en a const A&, ce que les opérateurs d'affectation et les constructeurs de copies attendent de leurs arguments.

Le cas bénin

B b;
A a = b;

Il ne se passe rien de mal là-bas - vous avez demandé une instance de Ace qui est une copie de B, et c'est exactement ce que vous obtenez. Bien sûr, ane contiendra pas certains de bses membres, mais comment cela devrait-il? C'est un A, après tout, pas un B, donc il n'a même pas entendu parler de ces membres, et encore moins serait en mesure de les stocker.

Le cas perfide

B b1;
B b2;
A& a_ref = b2;
a_ref = b1;
//b2 now contains a mixture of b1 and b2!

Vous pourriez penser que ce b2sera une copie de la b1suite. Mais, hélas, ce n'est pas ! Si vous l'inspectez, vous découvrirez qu'il b2s'agit d'une créature frankensteinienne, faite de quelques morceaux de b1(les morceaux qui Bhéritent de A) et de quelques morceaux de b2(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 ligne a_ref = b1appellera l'opérateur d'affectation de A, pas celui de B. En effet, pour les fonctions non virtuelles, le type déclaré (formellement: statique ) (qui est A&) détermine quelle fonction est appelée, par opposition au type réel (formellement: dynamique ) (qui le serait B, car a_refréférence une instance de B) . Maintenant, Al'opérateur d'affectation de ne connaît évidemment que les membres déclarés dans A, 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_castpour vérifier que l'objet affecté a un type compatible. La troisième étape consiste à faire l'affectation réelle dans un (protégé!) Membre assign(), depuis B« s assign()voudront probablement utiliser A» s assign()pour copier Ade membres.

class A {
public:
  virtual A& operator= (const A& a) {
    assign(a);
    return *this;
  }

protected:
  void assign(const A& a) {
    // copy members of A from a to this
  }
};

class B : public A {
public:
  virtual B& operator= (const A& a) {
    if (const B* b = dynamic_cast<const B*>(&a))
      assign(*b);
    else
      throw bad_assignment();
    return *this;
  }

protected:
  void assign(const B& b) {
    A::assign(b); // Let A's assign() copy members of A from b to this
    // copy members of B from b to this
  }
};

Notez que, pour plus de commodité, Bs operator=remplace de manière covariante le type de retour, car il sait qu'il retourne une instance de B.

fgp
la source
12
À mon humble avis, le problème est qu'il existe deux types différents de substituabilité qui peuvent être impliqués par l'héritage: soit n'importe quelle derivedvaleur peut être donnée au code attendant une basevaleur, 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.
supercat
16
Je ne comprends pas ce qui est si mauvais dans votre cas "perfide". Vous avez déclaré que vous vouliez: 1) obtenir une référence à un objet de classe A et 2) convertir l'objet b1 en classe A et copier ses éléments dans une référence de la classe A. Ce qui ne va pas ici, c'est la bonne logique derrière le code donné. En d'autres termes, vous avez pris un petit cadre d'image (A), vous l'avez placé sur une image plus grande (B) et vous avez peint à travers ce cadre, vous plaignant plus tard que votre image plus grande a maintenant l'air moche :) Mais si nous considérons simplement cette zone encadrée, il semble assez bon, tout comme le peintre le voulait, non? :)
Mladen B.
12
Le problème est, autrement dit, que C ++ par défaut suppose un type très fort de substituabilité - il nécessite que les opérations de la classe de base fonctionnent correctement sur les instances de sous-classe. Et cela même pour les opérations que le compilateur a générées automatiquement comme affectation. Il ne suffit donc pas de gâcher vos propres opérations à cet égard, vous devez également désactiver explicitement les mauvaises générées par le compilateur. Ou bien sûr, évitez l'héritage public, ce qui est généralement une bonne suggestion de toute façon ;-)
fgp
14
Une autre approche courante consiste à désactiver simplement l'opérateur de copie et d'affectation. Pour les classes dans la hiérarchie d'héritage, il n'y a généralement aucune raison d'utiliser la valeur au lieu de la référence ou du pointeur.
Siyuan Ren
13
Qu'est-ce que? Je n'avais aucune idée que les opérateurs pouvaient être marqués comme virtuels
paulm
155

Si vous avez une classe de base Aet une classe dérivée B, vous pouvez effectuer les opérations suivantes.

void wantAnA(A myA)
{
   // work with myA
}

B derived;
// work with the object "derived"
wantAnA(derived);

Maintenant, la méthode a wantAnAbesoin d'une copie de derived. Cependant, l'objet derivedne peut pas être copié complètement, car la classe Bpourrait inventer des variables membres supplémentaires qui ne sont pas dans sa classe de base A.

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, car

  • il peut être incomplet,
  • il se comporte comme un A-objet (tout comportement spécial de la classe Best perdu).
Noir
la source
41
C ++ n'est pas Java! Si wantAnA(comme son nom l'indique!) Veut un A, alors c'est ce qu'il obtient. Et une instance de A, se comportera, euh, comme un A. Comment est-ce surprenant?
fgp
83
@fgp: C'est surprenant, car vous ne passez pas un A à la fonction.
Black
10
@fgp: Le comportement est similaire. Cependant, pour le programmeur C ++ moyen, cela pourrait être moins évident. Pour autant que j'ai compris la question, personne ne "se plaint". Il s'agit simplement de la façon dont le compilateur gère la situation. À mon humble avis, il vaut mieux éviter de trancher en passant des références (const).
Noir
9
@ThomasW Non, je ne rejetterais pas l'héritage, mais j'utiliserais des références. Si la signature de wantAnA était nulle wantAnA (const A & myA) , il n'y avait pas eu de découpage. Au lieu de cela, une référence en lecture seule à l'objet de l'appelant est transmise.
noir
14
le problème concerne principalement la conversion automatique à partir de laquelle le compilateur effectue derivedle type A. 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.
pqnet
41

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:

#include <iostream>

using namespace std;

// Base class
class A {
public:
    A() {}
    A(const A& a) {
        cout << "'A' copy constructor" << endl;
    }
    virtual void run() const { cout << "I am an 'A'" << endl; }
};

// Derived class
class B: public A {
public:
    B():A() {}
    B(const B& a):A(a) {
        cout << "'B' copy constructor" << endl;
    }
    virtual void run() const { cout << "I am a 'B'" << endl; }
};

void g(const A & a) {
    a.run();
}

void h(const A a) {
    a.run();
}

int main() {
    cout << "Call by reference" << endl;
    g(B());
    cout << endl << "Call by copy" << endl;
    h(B());
}

La sortie est:

Call by reference
I am a 'B'

Call by copy
'A' copy constructor
I am an 'A'
geh
la source
Bonjour. Excellente réponse mais j'ai une question. Si je fais quelque chose comme ça ** dev d; base * b = & d; ** Le découpage a également lieu?
Adrian
@Adrian Si vous introduisez de nouvelles fonctions membres ou variables membres dans la classe dérivée, celles-ci ne sont pas directement accessibles depuis le pointeur de la classe de base. Cependant, vous pouvez toujours y accéder depuis l'intérieur des fonctions virtuelles de la classe de base surchargée. Voir ceci: godbolt.org/z/LABx33
Vishal Sharma
30

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.

L'Archétype Paul
la source
29

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.

Walter Bright
la source
3
Veuillez expliquer comment la corruption de mémoire peut se produire.
foraidt
4
J'ai oublié que la copie ctor réinitialisera le vptr, mon erreur. Mais vous pouvez toujours obtenir la corruption si A a un pointeur, et B le définit pour pointer dans la section de B qui est découpée.
Walter Bright
18
Ce problème ne se limite pas à la découpe. Toutes les classes qui contiennent des pointeurs vont avoir un comportement douteux avec un opérateur d'affectation par défaut et un constructeur de copie.
Weeble
2
@Weeble - C'est pourquoi vous remplacez le destructeur, l'opérateur d'affectation et le constructeur de copie par défaut dans ces cas.
Bjarke Freund-Hansen
7
@Weeble: Ce qui rend le découpage d'objets pire que les corrections générales de pointeurs, c'est que pour être certain que vous avez empêché le découpage, une classe de base doit fournir des constructeurs de conversion pour chaque classe dérivée . (Pourquoi? Toutes les classes dérivées qui sont manquées sont susceptibles d'être récupérées par le ctor de copie de la classe de base, car elles Derivedsont implicitement convertibles en Base.)
j_random_hacker
11

En C ++, un objet de classe dérivé peut être affecté à un objet de classe de base, mais l'inverse n'est pas possible.

class Base { int x, y; };

class Derived : public Base { int z, w; };

int main() 
{
    Derived d;
    Base b = d; // Object Slicing,  z and w of d are sliced off
}

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.

Kartik Maheshwari
la source
8

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 ++.

ididak
la source
5
" comportement d'objet" normal " " ce n'est pas un "comportement d'objet normal", c'est une sémantique de référence . Et cela n'a aucun rapport avec C struct, la compatibilité ou tout autre non-sens que tout prêtre OOP aléatoire vous a dit.
curiousguy
4
@curiousguy Amen, frère. Il est triste de voir à quelle fréquence le C ++ est critiqué parce qu'il n'est pas Java, lorsque la sémantique des valeurs est l'une des choses qui rend le C ++ si incroyablement puissant.
fgp
Ce n'est pas une fonctionnalité, pas une bizarrerie / erreur. C'est un comportement normal de copie sur pile, car appeler une fonction avec une variable de pile d'allocation arg ou (identique) de type Basedoit prendre exactement des sizeof(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 volatile
Croll
Certainement une erreur de C ++. L'affectation d'un objet dérivé à un objet de base doit être interdite, tandis que la liaison d'un objet dérivé à une référence ou à un pointeur de la classe de base doit être OK.
John Z. Li
7

Alors ... 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.

Steve Steiner
la source
3
" Vous pouvez également marquer explicitement le constructeur de copie sur la base ", ce qui n'aide pas du tout.
curiousguy
6

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

class Pet
{
 public:
    string name;
};
class Dog : public Pet
{
public:
    string breed;
};

int main()
{   
    Dog dog;
    Pet pet;

    dog.name = "Tommy";
    dog.breed = "Kangal Dog";
    pet = dog;
    cout << pet.breed; //ERROR

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

Pet *ptrP;
Dog *ptrD;
ptrD = new Dog;         
ptrD->name = "Tommy";
ptrD->breed = "Kangal Dog";
ptrP = ptrD;
cout << ((Dog *)ptrP)->breed; 

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.

haberdar
la source
7
Je comprends la partie "tranchage", mais je ne comprends pas "problème". Comment se fait-il qu'un problème dogne faisant pas partie de la classe Pet(le breedmembre de données) ne soit pas copié dans la variable pet? Le code ne s'intéresse qu'aux Petmembres 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.
curiousguy
4
" ((Dog *)ptrP)" Je suggère d'utiliserstatic_cast<Dog*>(ptrP)
curiousguy
Je suggère de souligner que vous ferez «se reproduire» la chaîne éventuellement une fuite de mémoire sans destructeur virtuel (le destructeur de «chaîne» ne sera pas appelé) lors de la suppression via «ptrP» ... Pourquoi ce que vous montrez problématique? Le correctif est principalement une conception de classe appropriée. Le problème dans ce cas est que l'écriture de constructeurs pour contrôler la visibilité lors de l'héritage est fastidieuse et facilement oubliée. Vous n'irez nulle part près de la zone de danger avec votre code car il n'y a aucun polymorphisme impliqué ou même mentionné (le découpage tronquera votre objet mais ne fera pas planter votre programme, ici).
Mec
24
-1 Cela n'explique pas complètement le problème réel. C ++ a une sémantique de valeur, pas une sémantique de référence comme Java, donc tout cela est tout à fait normal. Et le "correctif" est vraiment un exemple de code C ++ vraiment horrible . «Résoudre» des problèmes inexistants comme ce type de découpage en recourant à l'allocation dynamique est une recette pour du code bogué, une fuite de mémoire et des performances horribles. Notez qu'il existe sont les cas où le découpage est mauvais, mais cette réponse failes à les signaler. Astuce: le problème commence si vous attribuez des références .
fgp
Comprenez-vous même qu'essayer d'accéder à un membre de type non défini ( Dog::breed) n'est en aucun cas une ERREUR liée à la COUPE?
Croll
4

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.

Minok
la source
3
Mais rappelez-vous, Minok, que vous ne passez PAS une référence de cet objet. Vous passez une NOUVELLE copie de cet objet, mais vous utilisez la classe de base pour le copier dans le processus.
Arafangion
copie / affectation protégée sur la classe de base et ce problème est résolu.
Mec
1
Tu as raison. La bonne pratique consiste à utiliser des classes de base abstraites ou à restreindre l'accès à la copie / affectation. Cependant, ce n'est pas si facile à repérer une fois qu'il est là et facile à oublier de prendre soin de. L'appel de méthodes virtuelles avec sliced ​​* peut provoquer des choses mystérieuses si vous vous en sortez sans violation d'accès.
Mec
1
Je me souviens de mes cours de programmation C ++ à l'université qu'il y avait des meilleures pratiques permanentes pour chaque classe que nous avons créée, nous étions tenus d'écrire des constructeurs par défaut, des constructeurs de copie et des opérateurs d'affectation, ainsi qu'un destructeur. De cette façon, vous vous êtes assuré que la construction de la copie et similaires se produisaient comme vous en aviez besoin, tout en écrivant la classe ... plutôt que plus tard, un comportement étrange apparaissant.
Minok
3

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 classe fournit une affectation (accidentellement, éventuellement générée par le compilateur) sur une classe de base polymorphe.
  • Le client copie et découpe une instance d'une classe dérivée.
  • Le client appelle une fonction membre virtuelle qui accède à l'état découpé.
Mec
la source
3

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:

           class baseclass
          {
                 ...
                 baseclass & operator =(const baseclass&);
                 baseclass(const baseclass&);
          }
          void function( )
          {
                baseclass obj1=m;
                obj1=m;
          }

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.

Santosh
la source
1
class A 
{ 
    int x; 
};  

class B 
{ 
    B( ) : x(1), c('a') { } 
    int x; 
    char c; 
};  

int main( ) 
{ 
    A a; 
    B b; 
    a = b;     // b.c == 'a' is "sliced" off
    return 0; 
}
quidkid
la source
4
Pourriez-vous donner quelques détails supplémentaires? En quoi votre réponse diffère-t-elle de celles déjà publiées?
Alexis Pigeon
2
Je suppose que plus d'explications ne seraient pas mauvaises.
boucleur
-1

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.

class Base { 
int x;
 };

class Derived : public Base { 
 int z; 
 };

 int main() 
{
Derived d;
Base b = d; // Object Slicing,  z of d is sliced off
}
Varun Kumar
la source
-1

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:

#include<bits/stdc++.h>
using namespace std;
class Base
{
    public:
        int a;
        int b;
        int c;
        Base()
        {
            a=10;
            b=20;
            c=30;
        }
};
class Derived : public Base
{
    public:
        int d;
        int e;
        Derived()
        {
            d=40;
            e=50;
        }
};
int main()
{
    Derived d;
    cout<<d.a<<"\n";
    cout<<d.b<<"\n";
    cout<<d.c<<"\n";
    cout<<d.d<<"\n";
    cout<<d.e<<"\n";


    Base b = d;
    cout<<b.a<<"\n";
    cout<<b.b<<"\n";
    cout<<b.c<<"\n";
    cout<<b.d<<"\n";
    cout<<b.e<<"\n";
    return 0;
}

Il générera:

[Error] 'class Base' has no member named 'd'
[Error] 'class Base' has no member named 'e'
Ghulam Moinul Quadir
la source
Voté car ce n'est pas un bon exemple. Cela ne fonctionnerait pas non plus si, au lieu de copier d en b, vous utilisiez un pointeur, auquel cas d et e existeraient toujours mais Base n'a pas ces membres. Votre exemple montre seulement que vous ne pouvez pas accéder aux membres que la classe n'a pas.
Stefan Fabian
-2

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, Actiona quelques variables membres. Pour plus de simplicité, nous avons juste 2, étant un std::string nameet un std::function<void()> f. Ensuite, il a un void activate()qui exécute simplement le fmembre.

L'interface utilisateur est donc std::vector<Action>fournie. Imaginez quelques fonctions comme:

void push_back(Action toAdd);

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' Actionobjet. 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 Actionpour ajouter sa propre saveur.
Il passe une instance de sa classe brassée à la maison au push_backmais 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 fsont 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 As et Bs dérivant d'une manière ou d'une autre.

Martin B.
la source