Was ist Objektschneiden?


Antworten:


609

Beim "Schneiden" weisen Sie einer Instanz einer Basisklasse ein Objekt einer abgeleiteten Klasse zu, wodurch ein Teil der Informationen verloren geht - ein Teil davon wird "geschnitten".

Zum Beispiel,

class A {
   int foo;
};

class B : public A {
   int bar;
};

Ein Objekt vom Typ Bhat also zwei Datenelemente foound bar.

Dann, wenn Sie dies schreiben würden:

B b;

A a = b;

Dann gehen die Informationen büber das Mitglied barin verloren a.


66
Sehr informativ, aber unter stackoverflow.com/questions/274626#274636 finden Sie ein Beispiel dafür, wie das Slicing bei Methodenaufrufen erfolgt (was die Gefahr etwas besser unterstreicht als das Beispiel für die einfache Zuweisung).
Blair Conrad

55
Interessant. Ich programmiere seit 15 Jahren in C ++ und dieses Problem ist mir nie in den Sinn gekommen, da ich Objekte aus Gründen der Effizienz und des persönlichen Stils immer als Referenz übergeben habe. Zeigt, wie gute Gewohnheiten Ihnen helfen können.
Karl Bielefeldt

10
@Felix Danke, aber ich denke nicht, dass das Zurückwerfen (da keine Zeigerarithmetik funktioniert) funktionieren wird. Es A a = b; aist jetzt ein Objekt vom Typ, Adas eine Kopie von hat B::foo. Es wird ein Fehler sein, es jetzt zurück zu werfen, denke ich.

37
Dies ist kein "Schneiden" oder zumindest eine gutartige Variante davon. Das eigentliche Problem tritt auf, wenn Sie dies tun B b1; B b2; A& b2_ref = b2; b2 = b1. Man könnte denken , Sie kopiert haben , b1zu b2, aber Sie haben nicht! Sie haben einen Teil von b1to b2(den Teil von dem b1, der von Bgeerbt wurde A) kopiert und die anderen Teile von b2unverändert gelassen . b2ist jetzt eine frankensteinische Kreatur, die aus ein paar b1Teilen besteht, gefolgt von einigen Stücken b2. Pfui! Downvoting, weil ich denke, dass die Antwort sehr irreführend ist.
FGP

24
@fgp Ihr ​​Kommentar sollte lauten B b1; B b2; A& b2_ref = b2; b2_ref = b1" Das eigentliche Problem tritt auf, wenn Sie " ... von einer Klasse mit einem nicht virtuellen Zuweisungsoperator stammen. Ist Asogar zur Ableitung gedacht? Es hat keine virtuellen Funktionen. Wenn Sie von einem Typ abgeleitet sind, müssen Sie sich damit auseinandersetzen, dass seine Mitgliedsfunktionen aufgerufen werden können!
Neugieriger

510

Die meisten Antworten hier erklären nicht, was das eigentliche Problem beim Schneiden ist. Sie erklären nur die gutartigen Fälle des Schneidens, nicht die tückischen. Nehmen Sie an, wie bei den anderen Antworten, dass Sie es mit zwei Klassen zu tun haben Aund Bwoher B(öffentlich) stammt A.

In dieser Situation kann C ++ Sie eine Instanz übergeben Bzu A‚s Zuweisungsoperator (und auch den Kopierkonstruktors). Dies funktioniert, weil eine Instanz von Bin a konvertiert werden kann. Dies const A&erwarten Zuweisungsoperatoren und Kopierkonstruktoren von ihren Argumenten.

Der gutartige Fall

B b;
A a = b;

Dort passiert nichts Schlimmes - Sie haben nach einer Instanz gefragt, von Ader es sich um eine Kopie handelt B, und genau das erhalten Sie. Sicher, awird einige bMitglieder nicht enthalten , aber wie sollte es sein? Es ist einA immerhin nicht B, so hat es nicht einmal gehört , über diese Mitglieder, geschweige denn , wäre in der Lage , sie zu speichern.

Der tückische Fall

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

Sie könnten denken, dass b2dies eine Kopie von b1später sein wird. Aber leider nicht ! Wenn Sie es inspizieren, werden Sie feststellen, dass b2es sich um eine frankensteinische Kreatur handelt, die aus einigen Stücken von b1(den Stücken, Bdie von ihnen erben) bestehtA ) und einigen Stücken b2(der Stücke, die nur Benthalten) besteht. Autsch!

Was ist passiert? Nun, C ++ behandelt Zuweisungsoperatoren standardmäßig nicht als virtual. Somit a_ref = b1ruft die Leitung den Zuweisungsoperator von auf A, nicht den von B. Dies liegt daran, dass für nicht virtuelle Funktionen der deklarierte (formal: statische ) Typ (der ist A&) bestimmt, welche Funktion aufgerufen wird, im Gegensatz zum tatsächlichen (formal: dynamischen ) Typ (der wäre B, da er a_refauf eine Instanz von verweist B). . Jetzt Aweiß der Zuweisungsoperator offensichtlich nur über die in deklarierten Mitglieder Bescheid A, sodass nur diese kopiert werden und die hinzugefügten Mitglieder Bunverändert bleiben.

Eine Lösung

Das Zuweisen nur zu Teilen eines Objekts ist normalerweise wenig sinnvoll, aber C ++ bietet leider keine integrierte Möglichkeit, dies zu verbieten. Sie können jedoch Ihre eigenen rollen. Der erste Schritt besteht darin, den Zuweisungsoperator virtuell zu machen . Dies garantiert, dass immer der Zuweisungsoperator des tatsächlichen Typs aufgerufen wird, nicht der deklarierte Typ. Der zweite Schritt besteht darin, dynamic_castzu überprüfen, ob das zugewiesene Objekt einen kompatiblen Typ hat. Der dritte Schritt besteht darin, die eigentliche Zuweisung in einem (geschützten!) Mitglied durchzuführen assign(), da B's assign()wahrscheinlich A' s assign()zum Kopieren von AMitgliedern verwenden möchte .

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
  }
};

Beachten Sie, dass, für die reine Bequemlichkeit B‚s operator=kovariant überschreibt die Art Rückkehr, da er weiß , dass es eine Instanz ist zurückkehrt B.


12
IMHO besteht das Problem darin, dass es zwei verschiedene Arten der Substituierbarkeit gibt, die durch Vererbung impliziert werden können: Entweder derivedkann dem Code, der einen baseWert erwartet, ein beliebiger Wert zugewiesen werden , oder es kann eine abgeleitete Referenz als Basisreferenz verwendet werden. Ich würde gerne eine Sprache mit einem Typsystem sehen, das beide Konzepte getrennt behandelt. Es gibt viele Fälle, in denen eine abgeleitete Referenz durch eine Basisreferenz ersetzt werden sollte, abgeleitete Instanzen jedoch nicht durch Basisinstanzen ersetzt werden sollten. Es gibt auch viele Fälle, in denen Instanzen konvertierbar sein sollten, Referenzen jedoch nicht ersetzen sollten.
Supercat

16
Ich verstehe nicht, was in Ihrem "tückischen" Fall so schlimm ist. Sie haben angegeben, dass Sie: 1) eine Referenz auf ein Objekt der Klasse A erhalten und 2) das Objekt b1 in Klasse A umwandeln und seine Inhalte in eine Referenz der Klasse A kopieren möchten. Was hier tatsächlich falsch ist, ist die richtige Logik dahinter den angegebenen Code. Mit anderen Worten, Sie haben einen kleinen Bildrahmen (A) genommen, ihn über ein größeres Bild (B) gelegt und durch diesen Rahmen gemalt und sich später darüber beschwert, dass Ihr größeres Bild jetzt hässlich aussieht :) Aber wenn wir nur diesen gerahmten Bereich betrachten, es sieht ziemlich gut aus, so wie es der Maler wollte, oder? :)
Mladen B.

12
Anders ausgedrückt, das Problem besteht darin, dass C ++ standardmäßig eine sehr starke Substituierbarkeit voraussetzt - es erfordert, dass die Operationen der Basisklasse auf Instanzen von Unterklassen korrekt funktionieren. Und das auch für Operationen, die der Compiler wie eine Zuweisung automatisch generiert hat. Es reicht also nicht aus, Ihre eigenen Operationen in dieser Hinsicht nicht zu vermasseln, sondern Sie müssen auch die vom Compiler generierten falschen explizit deaktivieren. Oder halten Sie sich natürlich von der öffentlichen Erbschaft fern, was normalerweise immer ein guter Vorschlag ist ;-)
fgp

14
Ein weiterer gängiger Ansatz besteht darin, den Kopier- und Zuweisungsoperator einfach zu deaktivieren. Für Klassen innerhalb der Vererbungshierarchie gibt es normalerweise keinen Grund, Wert anstelle von Referenz oder Zeiger zu verwenden.
Siyuan Ren

13
Was zum? Ich hatte keine Ahnung, dass Operatoren als virtuell markiert werden könnten
Paulm

154

Wenn Sie eine Basisklasse Aund eine abgeleitete Klasse haben B, können Sie Folgendes tun.

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

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

Jetzt benötigt die Methode wantAnAeine Kopie von derived. Das Objekt derivedkann jedoch nicht vollständig kopiert werden, da die Klasse Bzusätzliche Elementvariablen erfinden könnte, die sich nicht in ihrer Basisklasse befinden A.

Zum Aufrufen wantAnA"schneidet" der Compiler daher alle zusätzlichen Mitglieder der abgeleiteten Klasse ab. Das Ergebnis könnte ein Objekt sein, das Sie nicht erstellen wollten, weil

  • es kann unvollständig sein,
  • es verhält sich wie ein A-Objekt (alles spezielle Verhalten der Klasse Bgeht verloren).

41
C ++ ist kein Java! Wenn wantAnA(wie der Name schon sagt!) Einen will A, dann ist es das, was es bekommt. Und eine Instanz von Awird sich wie eine verhalten A. Wie ist das überraschend?
FGP

83
@fgp: Es ist überraschend, weil Sie der Funktion kein A übergeben .
Schwarz

10
@fgp: Das Verhalten ist ähnlich. Für den durchschnittlichen C ++ - Programmierer ist dies jedoch möglicherweise weniger offensichtlich. Soweit ich die Frage verstanden habe, "beschwert" sich niemand. Es geht nur darum, wie der Compiler mit der Situation umgeht. Imho, es ist besser, das Schneiden überhaupt zu vermeiden, indem man (const) Referenzen übergibt.
Schwarz

9
@ThomasW Nein, ich würde keine Vererbung wegwerfen, sondern Referenzen verwenden. Wenn die Signatur von wantAnA nichtig wäre wantAnA (const A & myA) , dann wurde nicht geschnitten. Stattdessen wird ein schreibgeschützter Verweis auf das Objekt des Aufrufers übergeben.
Schwarz

14
Das Problem liegt hauptsächlich in der automatischen Umwandlung, die der Compiler derivedfür den Typ ausführt A. Implizites Casting ist in C ++ immer eine Quelle unerwarteten Verhaltens, da es oft schwer zu verstehen ist, wenn man den Code lokal betrachtet, dass ein Casting stattgefunden hat.
pqnet

41

Das sind alles gute Antworten. Ich möchte nur ein Ausführungsbeispiel hinzufügen, wenn Objekte nach Wert und Referenz übergeben werden:

#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());
}

Die Ausgabe ist:

Call by reference
I am a 'B'

Call by copy
'A' copy constructor
I am an 'A'

Hallo. Tolle Antwort, aber ich habe eine Frage. Wenn ich so etwas mache ** dev d; base * b = & d; ** Das Schneiden findet auch statt?
Adrian

@Adrian Wenn Sie einige neue Elementfunktionen oder Elementvariablen in die abgeleitete Klasse einführen, können Sie nicht direkt über den Basisklassenzeiger auf diese zugreifen. Sie können jedoch weiterhin über die überladenen virtuellen Funktionen der Basisklasse darauf zugreifen. Siehe dies: godbolt.org/z/LABx33
Vishal Sharma

30

Das dritte Match in Google für "C ++ Slicing" gibt mir diesen Wikipedia-Artikel http://en.wikipedia.org/wiki/Object_slicing und diesen (hitzig, aber die ersten paar Beiträge definieren das Problem): http://bytes.com/ forum / thread163565.html

Es ist also, wenn Sie der Superklasse ein Objekt einer Unterklasse zuweisen. Die Oberklasse kennt die zusätzlichen Informationen in der Unterklasse nicht und hat keinen Platz zum Speichern. Daher werden die zusätzlichen Informationen "abgeschnitten".

Wenn diese Links nicht genügend Informationen für eine "gute Antwort" enthalten, bearbeiten Sie bitte Ihre Frage, um uns mitzuteilen, wonach Sie mehr suchen.


29

Das Slicing-Problem ist schwerwiegend, da es zu einer Speicherbeschädigung führen kann und es sehr schwierig ist, sicherzustellen, dass ein Programm nicht darunter leidet. Um es außerhalb der Sprache zu entwerfen, sollten Klassen, die die Vererbung unterstützen, nur als Referenz (nicht nach Wert) zugänglich sein. Die Programmiersprache D hat diese Eigenschaft.

Betrachten Sie Klasse A und Klasse B, die von A abgeleitet sind. Eine Speicherbeschädigung kann auftreten, wenn der A-Teil einen Zeiger p und eine B-Instanz hat, die p auf die zusätzlichen Daten von B zeigt. Wenn dann die zusätzlichen Daten abgeschnitten werden, zeigt p auf Müll.


3
Bitte erläutern Sie, wie die Speicherbeschädigung auftreten kann.
Foraidt

4
Ich habe vergessen, dass der Kopierer den vptr zurücksetzt, mein Fehler. Aber Sie können immer noch Korruption bekommen, wenn A einen Zeiger hat und B diesen so setzt, dass er in den Abschnitt von B zeigt, der abgeschnitten wird.
Walter Bright

18
Dieses Problem beschränkt sich nicht nur auf das Schneiden. Alle Klassen, die Zeiger enthalten, weisen mit einem Standardzuweisungsoperator und einem Kopierkonstruktor ein zweifelhaftes Verhalten auf.
Weeble

2
@Weeble - Aus diesem Grund überschreiben Sie in diesen Fällen den Standarddestruktor, den Zuweisungsoperator und den Kopierkonstruktor.
Bjarke Freund-Hansen

7
@Weeble: Was das Slicing von Objekten schlechter macht als allgemeine Zeigerkorrekturen, ist, dass eine Basisklasse konvertierende Konstruktoren für jede abgeleitete Klasse bereitstellen muss, um sicherzugehen, dass Sie das Slicing verhindert haben . (Warum? Abgeleitete abgeleitete Klassen können vom Kopierer der Basisklasse erfasst werden, da sie Derivedimplizit in konvertiert werden können Base.) Dies widerspricht offensichtlich dem Open-Closed-Prinzip und ist mit einem hohen Wartungsaufwand verbunden.
j_random_hacker

11

In C ++ kann ein abgeleitetes Klassenobjekt einem Basisklassenobjekt zugewiesen werden, der andere Weg ist jedoch nicht möglich.

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
}

Das Aufteilen von Objekten erfolgt, wenn ein abgeleitetes Klassenobjekt einem Basisklassenobjekt zugewiesen wird. Zusätzliche Attribute eines abgeleiteten Klassenobjekts werden abgeschnitten, um das Basisklassenobjekt zu bilden.


8

Das Slicing-Problem in C ++ ergibt sich aus der Wertesemantik seiner Objekte, die hauptsächlich aufgrund der Kompatibilität mit C-Strukturen erhalten blieb. Sie müssen eine explizite Referenz- oder Zeigersyntax verwenden, um ein "normales" Objektverhalten zu erzielen, das in den meisten anderen Sprachen für Objekte zu finden ist, dh Objekte werden immer als Referenz weitergegeben.

Die kurze Antwort lautet, dass Sie das Objekt in Scheiben schneiden, indem Sie einem Basisobjekt ein abgeleitetes Objekt nach Wert zuweisen , dh das verbleibende Objekt ist nur ein Teil des abgeleiteten Objekts. Um die Wertesemantik zu erhalten, ist das Schneiden ein vernünftiges Verhalten und hat relativ seltene Verwendungszwecke, die in den meisten anderen Sprachen nicht vorhanden sind. Einige Leute betrachten es als eine Funktion von C ++, während viele es als eine der Macken / Fehlfunktionen von C ++ betrachteten.


5
" " normales "Objektverhalten " das ist nicht "normales Objektverhalten", das ist Referenzsemantik . Und es bezieht sich in keiner Weise auf C struct, Kompatibilität oder andere Unsinnigkeiten, die Ihnen ein zufälliger OOP-Priester gesagt hat.
Neugieriger

4
@curiousguy Amen, Bruder. Es ist traurig zu sehen, wie oft C ++ davon abgehalten wird, kein Java zu sein, wenn die Wertesemantik eines der Dinge ist, die C ++ so wahnsinnig mächtig machen.
FGP

Dies ist keine Funktion, keine Eigenart / Fehlfunktion. Es ist ein normales Verhalten beim Kopieren auf dem Stapel, da das Aufrufen einer Funktion mit einem Argument oder einer (gleichen) zuweisenden Stapelvariablen vom Typ Basegenau sizeof(Base)Bytes im Speicher benötigen muss , mit möglicher Ausrichtung, möglicherweise deshalb "Zuweisung" (Kopieren auf dem Stapel) ) kopiert keine abgeleiteten Klassenmitglieder, ihre Offsets liegen außerhalb der Größe von. Um "Datenverlust" zu vermeiden, verwenden Sie einfach wie alle anderen Zeiger, da der Zeigerspeicher an Ort und Stelle und in der Größe festgelegt ist, während der Stapel sehr flüchtig ist
Croll

Auf jeden Fall eine Fehlfunktion von C ++. Das Zuweisen eines abgeleiteten Objekts zu einem Basisobjekt sollte gesperrt werden, während das Binden eines abgeleiteten Objekts an eine Referenz oder einen Zeiger der Basisklasse in Ordnung sein sollte.
John Z. Li

7

Also ... Warum ist es schlecht, die abgeleiteten Informationen zu verlieren? ... weil der Autor der abgeleiteten Klasse die Darstellung möglicherweise so geändert hat, dass das Abschneiden der zusätzlichen Informationen den vom Objekt dargestellten Wert ändert. Dies kann passieren, wenn die abgeleitete Klasse zum Zwischenspeichern einer Darstellung verwendet wird, die für bestimmte Operationen effizienter ist, deren Rücktransformation in die Basisdarstellung jedoch teuer ist.

Ich dachte auch, jemand sollte auch erwähnen, was Sie tun sollten, um ein Schneiden zu vermeiden ... Holen Sie sich eine Kopie der C ++ - Codierungsstandards, 101 Richtlinien und Best Practices. Der Umgang mit dem Schneiden ist # 54.

Es wird ein etwas ausgefeiltes Muster vorgeschlagen, um das Problem vollständig zu lösen: einen geschützten Kopierkonstruktor, einen geschützten reinen virtuellen DoClone und einen öffentlichen Klon mit einer Zusicherung, die Ihnen sagt, ob eine (weitere) abgeleitete Klasse DoClone nicht korrekt implementiert hat. (Die Klonmethode erstellt eine korrekte tiefe Kopie des polymorphen Objekts.)

Sie können den Kopierkonstruktor auch explizit auf der Basis markieren, um bei Bedarf ein explizites Schneiden zu ermöglichen.


3
" Sie können den Kopierkonstruktor auch explizit auf der Basis markieren ", was überhaupt nicht hilft.
Neugieriger

6

1. DIE DEFINITION DES SCHNITTPROBLEMS

Wenn D eine abgeleitete Klasse der Basisklasse B ist, können Sie einer Variablen (oder einem Parameter) vom Typ Base ein Objekt vom Typ Abgeleitet zuweisen.

BEISPIEL

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

Obwohl die obige Zuordnung zulässig ist, verliert der Wert, der dem variablen Haustier zugewiesen wird, sein Rassenfeld. Dies wird als Schneidproblem bezeichnet .

2. BEHEBEN DES SCHNITTPROBLEMS

Um das Problem zu beheben, verwenden wir Zeiger auf dynamische Variablen.

BEISPIEL

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

In diesem Fall geht keines der Datenelemente oder Elementfunktionen der dynamischen Variablen verloren, auf die ptrD (untergeordnetes Klassenobjekt) zeigt. Wenn Sie Funktionen verwenden müssen, muss die Funktion außerdem eine virtuelle Funktion sein.


7
Ich verstehe den Teil "Schneiden", aber ich verstehe das "Problem" nicht. Wie ist es ein Problem, dass ein Zustand, der dognicht Teil der Klasse ist Pet(das breedDatenelement), nicht in die Variable kopiert wird pet? Der Code interessiert sich Petanscheinend nur für die Datenelemente. Schneiden ist definitiv ein "Problem", wenn es unerwünscht ist, aber das sehe ich hier nicht.
Neugieriger

4
" ((Dog *)ptrP)Ich schlage vor, zu verwendenstatic_cast<Dog*>(ptrP)
neugieriger Kerl

Ich schlage vor, darauf hinzuweisen, dass die Zeichenfolge beim Löschen über "ptrP" möglicherweise Speicher ohne virtuellen Destruktor verliert (der Destruktor von "Zeichenfolge" wird nicht aufgerufen). Warum ist das, was Sie anzeigen, problematisch? Das Update ist meistens das richtige Klassendesign. Das Problem in diesem Fall ist, dass das Aufschreiben von Konstruktoren zur Steuerung der Sichtbarkeit beim Erben mühsam ist und leicht vergessen wird. Mit Ihrem Code kommen Sie nicht in die Nähe der Gefahrenzone, da kein Polymorphismus vorliegt oder sogar erwähnt wird (durch das Schneiden wird Ihr Objekt abgeschnitten, Ihr Programm stürzt hier jedoch nicht ab).
Alter

24
-1 Dies erklärt das eigentliche Problem nicht vollständig. C ++ hat eine Wertesemantik, keine Referenzsemantik wie Java, daher ist dies alles zu erwarten. Und das "Update" ist wirklich ein Beispiel für wirklich schrecklichen C ++ - Code. Das "Beheben" nicht vorhandener Probleme wie dieser Art des Slicing durch Rückgriff auf dynamische Zuordnung ist ein Rezept für fehlerhaften Code, Speicherverlust und schreckliche Leistung. Beachten Sie, dass es Fälle gibt, in denen das Schneiden schlecht ist, diese Antwort jedoch nicht darauf hinweist. Hinweis: Der Fehler beginnt, wenn Sie durch Referenzen zuweisen .
FGP

Verstehst du überhaupt, dass der Versuch, auf ein Mitglied eines Typs zuzugreifen, der nicht definiert ist ( Dog::breed), in keiner Weise ein FEHLER im Zusammenhang mit SLICING ist?
Croll

4

Es scheint mir, dass das Schneiden nicht so sehr ein Problem ist, als wenn Ihre eigenen Klassen und Programme schlecht aufgebaut / entworfen sind.

Wenn ich ein Unterklassenobjekt als Parameter an eine Methode übergebe, die einen Parameter vom Typ Superklasse verwendet, sollte ich mir dessen bewusst sein und wissen, dass die aufgerufene Methode intern nur mit dem Objekt der Oberklasse (auch bekannt als Basisklasse) funktioniert.

Es scheint mir nur die unvernünftige Erwartung, dass die Bereitstellung einer Unterklasse, in der eine Basisklasse angefordert wird, irgendwie zu unterklassenspezifischen Ergebnissen führen und das Schneiden zu einem Problem machen würde. Es ist entweder ein schlechtes Design bei der Verwendung der Methode oder eine schlechte Implementierung der Unterklasse. Ich vermute, dass dies normalerweise das Ergebnis eines Opfers eines guten OOP-Designs zugunsten von Zweckmäßigkeit oder Leistungssteigerungen ist.


3
Aber denken Sie daran, Minok, dass Sie KEINE Referenz dieses Objekts übergeben. Sie übergeben eine NEUE Kopie dieses Objekts, verwenden es jedoch mithilfe der Basisklasse.
Arafangion

geschützte Kopie / Zuweisung auf der Basisklasse und dieses Problem ist gelöst.
Geck

1
Du hast recht. Es wird empfohlen, abstrakte Basisklassen zu verwenden oder den Zugriff auf das Kopieren / Zuweisen zu beschränken. Es ist jedoch nicht so leicht zu erkennen, wenn es einmal da ist, und es ist leicht zu vergessen, sich darum zu kümmern. Wenn Sie virtuelle Methoden mit Sliced ​​* aufrufen, können mysteriöse Dinge passieren, wenn Sie ohne Zugriffsverletzung davonkommen.
Geck

1
Ich erinnere mich an meine C ++ - Programmierkurse an der Universität, dass es Best Practices gab, nach denen wir für jede von uns erstellte Klasse Standardkonstruktoren, Kopierkonstruktoren und Zuweisungsoperatoren sowie einen Destruktor schreiben mussten. Auf diese Weise haben Sie sichergestellt, dass die Kopierkonstruktion und dergleichen so ablief, wie Sie es brauchten, während Sie die Klasse geschrieben haben ... anstatt später ein merkwürdiges Verhalten aufzutauchen.
Minok

3

OK, ich werde es versuchen, nachdem ich viele Beiträge gelesen habe, in denen das Schneiden von Objekten erklärt wird, aber nicht, wie es problematisch wird.

Das bösartige Szenario, das zu einer Speicherbeschädigung führen kann, ist das folgende:

  • Die Klasse bietet eine (versehentlich, möglicherweise vom Compiler generierte) Zuweisung für eine polymorphe Basisklasse.
  • Der Client kopiert und schneidet eine Instanz einer abgeleiteten Klasse.
  • Der Client ruft eine virtuelle Mitgliedsfunktion auf, die auf den Status "Abgeschnitten" zugreift.

3

Slicing bedeutet, dass die von einer Unterklasse hinzugefügten Daten verworfen werden, wenn ein Objekt der Unterklasse übergeben oder als Wert oder von einer Funktion zurückgegeben wird, die ein Basisklassenobjekt erwartet.

Erläuterung: Beachten Sie die folgende Klassendeklaration:

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

Da die Kopierfunktionen der Basisklasse nichts über das Abgeleitete wissen, wird nur der Basisteil des Abgeleiteten kopiert. Dies wird üblicherweise als Schneiden bezeichnet.


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; 
}

4
Würde es Ihnen etwas ausmachen, einige zusätzliche Details anzugeben? Wie unterscheidet sich Ihre Antwort von den bereits veröffentlichten?
Alexis Pigeon

2
Ich denke, dass mehr Erklärung nicht schlecht wäre.
Looper

-1

Wenn ein abgeleitetes Klassenobjekt einem Basisklassenobjekt zugewiesen wird, werden zusätzliche Attribute eines abgeleiteten Klassenobjekts vom Basisklassenobjekt abgeschnitten (verworfen).

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
}

-1

Wenn dem Basisklassenobjekt ein abgeleitetes Klassenobjekt zugewiesen wird, werden alle Mitglieder des abgeleiteten Klassenobjekts in das Basisklassenobjekt kopiert, mit Ausnahme der Elemente, die in der Basisklasse nicht vorhanden sind. Diese Mitglieder werden vom Compiler entfernt. Dies wird als Objektschneiden bezeichnet.

Hier ist ein Beispiel:

#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;
}

Es wird generiert:

[Error] 'class Base' has no member named 'd'
[Error] 'class Base' has no member named 'e'

Abgestimmt, weil das kein gutes Beispiel ist. Es würde auch nicht funktionieren, wenn Sie anstelle von d nach b einen Zeiger verwenden würden. In diesem Fall wären d und e noch vorhanden, aber Base hat diese Mitglieder nicht. Ihr Beispiel zeigt nur, dass Sie nicht auf Mitglieder zugreifen können, die die Klasse nicht hat.
Stefan Fabian

-2

Ich bin gerade auf das Problem des Schneidens gestoßen und bin sofort hier gelandet. Lassen Sie mich also meine zwei Cent dazu addieren.

Lassen Sie uns ein Beispiel aus "Produktionscode" (oder etwas, das irgendwie nahe kommt) haben:


Nehmen wir an, wir haben etwas, das Aktionen auslöst. Eine Control Center-Benutzeroberfläche zum Beispiel.
Diese Benutzeroberfläche muss eine Liste der Dinge erhalten, die derzeit versendet werden können. Also definieren wir eine Klasse, die die Versandinformationen enthält. Nennen wir es Action. Also Actionhat ein einige Mitgliedsvariablen. Der Einfachheit halber haben wir nur 2, a std::string nameund a std::function<void()> f. Dann hat es eine, void activate()die gerade die ausführtf Mitglied .

So wird die Benutzeroberfläche std::vector<Action>mitgeliefert. Stellen Sie sich einige Funktionen vor wie:

void push_back(Action toAdd);

Jetzt haben wir festgelegt, wie es aus der Sicht der Benutzeroberfläche aussieht. Bisher kein Problem. Aber ein anderer Typ, der an diesem Projekt arbeitet, entscheidet plötzlich, dass es spezielle Aktionen gibt, die mehr Informationen benötigenAction Objekt . Aus welchem ​​Grund auch immer. Das könnte auch mit Lambda-Captures gelöst werden. Dieses Beispiel wird nicht 1-1 aus dem Code übernommen.

Also leitet der Typ Actionseinen eigenen Geschmack ab.
Er gibt eine Instanz seiner selbst gebrauten Klasse an die weiter, push_backaber dann geht das Programm durcheinander.

Also was ist passiert?
Wie Sie vielleicht erraten haben: Das Objekt wurde in Scheiben geschnitten.

Die zusätzlichen Informationen aus der Instanz sind verloren gegangen und fneigen jetzt zu undefiniertem Verhalten.


Ich hoffe, dieses Beispiel bringt Licht für diejenigen Menschen, die sich Dinge nicht wirklich vorstellen können, wenn sie davon sprechen, dass As und Bs auf irgendeine Weise abgeleitet werden.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.