Question Qu'est-ce qu'un découpage d'objet?


Quelqu'un l'a mentionné dans l'IRC, mais google n'a pas une bonne réponse.


608
2017-11-08 11:10


origine


Réponses:


"Tranchage" est l'endroit où vous assignez un objet d'une classe dérivée à une instance d'une classe de base, perdant ainsi une partie de l'information - une partie est "découpée".

Par exemple,

class A {
   int foo;
};

class B : public A {
   int bar;
};

Donc un objet de type B a deux membres de données, foo et bar.

Alors si vous deviez écrire ceci:

B b;

A a = b;

Ensuite, l'information dans b à propos de membre bar est perdu dans a.


517
2017-11-08 11:22



La plupart des réponses ne parviennent pas à expliquer ce qu'est le problème réel du découpage. Ils expliquent seulement les cas bénins de tranchage, pas les tricheries. Supposons, comme les autres réponses, que vous avez affaire à deux classes A et B, où B dérive (publiquement) de A.

Dans cette situation, C ++ vous permet de passer une instance de B à Al'opérateur d'affectation (et aussi au constructeur de copie). Cela fonctionne parce qu'une instance de B peut être converti en un const A&, qui est ce que les opérateurs d'affectation et les constructeurs de copie attendent de leurs arguments.

Le cas bénin

B b;
A a = b;

Il ne se passe rien de mal ici - vous avez demandé une instance de A qui est une copie de Bet c'est exactement ce que vous obtenez. Sûr, a ne contiendra pas certains bles membres, mais comment devrait-il? C'est un Aaprès tout, pas un B, donc il n'a même pas entendu à propos de ces membres, 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 b2 sera une copie de b1 ensuite. Mais, hélas, c'est ne pas! Si vous l'inspectez, vous découvrirez que b2 est une créature Frankensteinienne, faite de quelques morceaux de b1 (les morceaux qui B hérite de A), et quelques morceaux de b2 (les morceaux qui seulement B contient). 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 = b1 appellera l'opérateur de cession de A, pas celui de B. C'est parce que pour les fonctions non-virtuelles, le déclaré type (qui est A&) détermine quelle fonction est appelée, par opposition à réel type (qui serait B, depuis a_ref fait référence à une instance de B). À présent, Al'opérateur de cession sait évidemment seulement sur les membres déclarés dans A, donc il ne copiera que ceux-là, laissant les membres ajoutés B inchangé.

Une solution

Assigner seulement à des parties d'un objet n'a généralement pas beaucoup de sens, mais C ++ ne fournit malheureusement pas de moyen intégré pour l'interdire. Vous pouvez cependant rouler le vôtre. La première étape consiste à faire l'opérateur d'affectation virtuel. Cela garantira que c'est toujours le réel l'opérateur d'affectation de type qui est appelé, pas le déclaré les types. 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 membre (protégé!) assign(), depuis Bde assign() voudra probablement utiliser Ade assign() copier ALes 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é, Bde operator= remplace de manière covariante le type de retour, car il sait qu'il retourne une instance de B.


396
2018-01-22 15:00



Si vous avez une classe de base A et une classe dérivée B, alors vous pouvez faire ce qui suit.

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

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

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

Par conséquent, appeler wantAnA, le compilateur "découpera" tous les membres supplémentaires de la classe dérivée. Le résultat pourrait être un objet que vous ne vouliez pas créer, car

  • il peut être incomplet,
  • il se comporte comme un A-object (tous les comportements spéciaux de la classe B est perdu).

134
2017-11-08 11:28



Troisième match dans google pour "C ++ slicing" me donne cet article Wikipedia http://en.wikipedia.org/wiki/Object_slicing et ceci (chauffé, mais les premiers messages définissent le problème): http://bytes.com/forum/thread163565.html

C'est donc quand vous assignez un objet d'une sous-classe à la super classe. La superclasse ne connaît rien des informations supplémentaires de la sous-classe et n'a pas la possibilité de les stocker, de sorte que les informations supplémentaires sont "tronquées".

Si ces liens ne donnent pas assez d'informations pour une "bonne réponse", veuillez éditer votre question pour nous faire savoir ce que vous cherchez.


30
2017-11-08 11:14



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'

28
2017-08-22 18:33



Le problème du découpage est sérieux 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 de la langue, les classes qui supportent l'héritage devraient être accessibles par référence seulement (pas par valeur). Le langage de programmation D a cette propriété.

Considérons la classe A et la classe B dérivée de A. La corruption de la mémoire peut se produire si la partie A a un pointeur p, et une instance B qui pointe vers les données supplémentaires de B. Ensuite, lorsque les données supplémentaires sont coupées, p pointe vers la poubelle.


24
2017-11-08 11:56



Le problème du découpage en C ++ provient de la sémantique des valeurs de ses objets, qui est restée principalement due à la compatibilité avec les structures C. Vous devez utiliser une syntaxe de référence ou de pointeur explicite pour obtenir un comportement d'objet "normal" dans la plupart des autres langages qui font des objets, c'est-à-dire que les objets sont toujours transmis par référence.

Les réponses courtes sont que vous triez l'objet en affectant un objet dérivé à un objet de base en valeurc'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 des utilisations relativement rares, qui n'existent pas dans la plupart des autres langages. Certaines personnes le considèrent comme une caractéristique de C ++, alors que beaucoup le considèrent comme l'une des bizarreries / erreurs de C ++.


8
2017-11-09 00:31



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 un 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 de compagnie variable perd son champ de race. C'est ce qu'on appelle le problème de tranchage.

2. COMMENT FIXER LE PROBLÈME DE TRANCHAGE

Pour contourner le problème, nous utilisons des pointeurs sur les 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 être pointé par ptrD (objet de classe descendant) sera perdu. De plus, si vous avez besoin d'utiliser des fonctions, la fonction doit être une fonction virtuelle.


8
2018-01-28 18:00



Alors ... Pourquoi est-ce que la perte de l'information dérivée est mauvaise? ... parce que l'auteur de la classe dérivée peut avoir changé la représentation de telle sorte que le découpage de l'information supplémentaire 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 plus coûteuse pour revenir à la représentation de base.

Aussi pensé que quelqu'un devrait aussi mentionner ce que vous devriez faire pour éviter de trancher ... Obtenez une copie des normes de codage C ++, des règles de règles 101 et des meilleures pratiques. Traiter avec le découpage est # 54.

Il suggère un modèle assez sophistiqué pour traiter complètement 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 (ultérieure) n'a pas implémenté correctement DoClone. (La méthode Clone fait une copie profonde appropriée de l'objet polymorphe.)

Vous pouvez également marquer le constructeur de la copie sur la base explicite, ce qui permet un découpage explicite si vous le souhaitez.


6
2017-11-08 17:38