Qu'est-ce que le tranchage d'objet?

Nov 08 2008

Quelqu'un l'a mentionné dans l'IRC comme étant le problème du découpage.

Réponses

635 DavidDibben Nov 08 2008 at 18:22

"Slicing" 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 de l'information - une partie est "découpée".

Par exemple,

class A {
   int foo;
};

class B : public A {
   int bar;
};

Ainsi, un objet de type Ba deux membres de données, fooet bar.

Ensuite, si vous deviez écrire ceci:

B b;

A a = b;

Ensuite, les informations bsur le membre barsont perdues a.

530 fgp Jan 22 2013 at 22:00

La plupart des réponses ici ne parviennent pas à expliquer quel est le problème réel du tranchage. Ils n'expliquent que les cas bénins de tranchage, pas les traîtres. Supposons, comme les autres réponses, que vous avez affaire à deux classes Aet d' BBvient (publiquement) A.

Dans cette situation, C ++ vous permet de passer une instance de l' opérateur d'affectation de Bto A(ainsi qu'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 copie attendent de leurs arguments.

Le cas bénin

B b;
A a = b;

Rien de mal ne se passe là-bas - vous avez demandé une instance Adont est une copie B, et c'est exactement ce que vous obtenez. Bien sûr, ane contiendra pas certains de bses membres, mais comment le 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 par la b1suite. Mais, hélas, ce n'est pas le cas ! 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 ne Bcontiennent que). Aie!

Qu'est ce qui c'est passé? 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 serait B, puisque a_refréférence une instance de B) . Désormais, Al'opérateur d'affectation ne connaît évidemment que les membres déclarés dans A, donc il ne copiera que ceux-ci, laissant les membres ajoutés Binchangés.

Une solution

Assigner uniquement à des parties d'un objet n'a généralement pas de sens, mais C ++, malheureusement, ne fournit aucun moyen intégré de 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é, et non 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 est de faire l 'affectation réelle dans un membre (protégé!) assign(), Puisque Bles membres assign()voudront probablement utiliser Ales assign()pour 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é, Bs operator=remplace de manière covariante le type de retour, car il sait qu'il renvoie une instance de B.

158 Black Nov 08 2008 at 18:28

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 vouliez pas créer, car

  • il peut être incomplet,
  • il se comporte comme un A-objet (tout comportement spécial de la classe Best perdu).
42 geh Aug 23 2014 at 01:33

Ce sont toutes de bonnes réponses. Je voudrais simplement 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'
30 TheArchetypalPaul Nov 08 2008 at 18:14

La troisième correspondance dans Google pour le "découpage C ++" me donne cet article Wikipédia http://en.wikipedia.org/wiki/Object_slicing et ceci (chauffé, mais les premiers articles 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 de la sous-classe, et n'a pas de place pour les stocker, donc les informations supplémentaires sont "découpées".

Si ces liens ne fournissent pas suffisamment d'informations pour une "bonne réponse", veuillez modifier votre question pour nous indiquer ce que vous recherchez de plus.

29 WalterBright Nov 08 2008 at 18:56

Le problème de 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 du langage, les classes qui prennent en charge l'héritage doivent être accessibles par référence uniquement (pas par valeur). Le langage de programmation D possède cette propriété.

Considérez la classe A et la classe B dérivée de A. Une 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 découpées, p pointe vers des déchets.

11 KartikMaheshwari Mar 07 2018 at 16:35

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'objets se produit lorsqu'un objet de classe dérivé est affecté à un objet de classe de base, des attributs supplémentaires d'un objet de classe dérivé sont découpés pour former l'objet de classe de base.

7 SteveSteiner Nov 09 2008 at 00:38

Alors ... Pourquoi la perte des informations dérivées est-elle mauvaise? ... parce que l'auteur de la classe dérivée peut avoir changé la représentation de telle sorte que le découpage 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.

J'ai également pensé que quelqu'un devrait également mentionner ce que vous devriez faire pour éviter le découpage ... Obtenez une copie des normes de codage C ++, des règles 101 et des meilleures pratiques. Le tranchage est le n ° 54.

Il suggère un modèle quelque peu sophistiqué pour traiter complètement le problème: avoir un constructeur de copie protégée, 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 réussi à implémenter DoClone correctement. (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.

7 ididak Nov 09 2008 at 07:31

Le problème de découpage en C ++ provient de la sémantique de 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 passés par référence.

La réponse courte est que vous découpez l'objet en attribuant 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 en tranches est un comportement raisonnable et a ses utilisations relativement rares, ce qui n'existe pas dans la plupart des autres langages. Certaines personnes le considèrent comme une fonctionnalité du C ++, tandis que beaucoup le considèrent comme l'une des bizarreries / dysfonctionnements du C ++.

6 haberdar Jan 29 2012 at 01:00

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 Dérivé à 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 attribuée à l'animal de compagnie variable perd son champ de race. C'est ce qu'on appelle le problème du tranchage .

2. COMMENT RÉSOUDRE LE PROBLÈME DE TRANCHAGE

Pour contourner 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.

4 Minok Jul 25 2009 at 02:45

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 superclass (aka baseclass).

Il me semble que l'attente déraisonnable selon laquelle le fait de fournir une sous-classe lorsqu'une classe de base est demandée, entraînerait d'une manière ou d'une autre des résultats spécifiques à une sous-classe, poserait un problème de découpage. C'est soit une mauvaise conception dans l'utilisation de la méthode, soit une mauvaise implémentation de sous-classe. J'imagine que c'est généralement le résultat de sacrifier une bonne conception de la POO au profit de gains de rapidité ou de performances.

3 Dude Oct 18 2012 at 10:22

OK, je vais essayer après avoir lu de nombreux articles expliquant le découpage des objets, mais pas en quoi cela devient problématique.

Le scénario vicieux qui peut entraîner une corruption de la mémoire est le suivant:

  • La classe fournit (accidentellement, éventuellement générée par le compilateur) une affectation 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é.
3 Santosh Mar 13 2014 at 01:08

Le découpage signifie que les données ajoutées par une sous-classe sont ignorées lorsqu'un objet de la sous-classe est passé ou renvoyé par valeur ou par 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. C'est ce qu'on appelle communément le tranchage.

1 quidkid Nov 29 2012 at 19:32
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; 
}
1 Sorush Sep 12 2020 at 18:27

Je vois toutes les réponses mentionnées lorsque le découpage d'objet se produit lorsque les membres de données sont découpés. Ici, je donne un exemple que les méthodes ne sont pas remplacées:

class A{
public:
    virtual void Say(){
        std::cout<<"I am A"<<std::endl;
    }
};

class B: public A{
public:
    void Say() override{
        std::cout<<"I am B"<<std::endl;
    }
};

int main(){
   B b;
   A a1;
   A a2=b;

   b.Say(); // I am B
   a1.Say(); // I am A
   a2.Say(); // I am A   why???
}

B (objet b) est dérivé de A (objets a1 et a2). b et a1, comme on s'y attend, appellent leur fonction membre. Mais du point de vue du polymorphisme, nous ne nous attendons pas à ce que a2, qui est assigné par b, ne soit pas écrasé. Fondamentalement, a2 enregistre uniquement une partie de classe A de b et c'est le découpage d'objets en C ++.

Pour résoudre ce problème, une référence ou un pointeur doit être utilisé

 A& a2=b;
 a2.Say(); // I am B

ou

A* a2 = &b;
a2->Say(); // I am B

Pour plus de détails voir mon post