Was ist Objekt-Slicing?

Lesezeit: 11 Minuten

Was ist Objekt Slicing
Frankomanie

Jemand erwähnte es im IRC als das Slicing-Problem.

Was ist Objekt Slicing
David Dibben

„Slicing“ bedeutet, dass Sie ein Objekt einer abgeleiteten Klasse einer Instanz einer Basisklasse zuweisen und dabei einen Teil der Informationen verlieren – einige davon werden „weggeschnitten“.

Zum Beispiel,

class A {
   int foo;
};

class B : public A {
   int bar;
};

Also ein Objekt vom Typ B hat zwei Datenmitglieder, foo und bar.

Wenn du dann das schreiben würdest:

B b;

A a = b;

Dann die Informationen rein b über Mitglied bar ist darin verloren a.

  • Sehr informativ, aber unter stackoverflow.com/questions/274626#274636 finden Sie ein Beispiel dafür, wie Slicing während Methodenaufrufen auftritt (was die Gefahr etwas besser unterstreicht als das einfache Zuweisungsbeispiel).

    – Blair Conrad

    8. November 2008 um 13:53 Uhr

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

    – Karl Bielefeldt

    2. Februar 2011 um 3:48 Uhr

  • @Felix Danke, aber ich glaube nicht, dass das Zurückwerfen (da keine Zeigerarithmetik) funktionieren wird, A a = b; a ist jetzt Objekt vom Typ A die eine Kopie von hat B::foo. Es wird ein Fehler sein, es jetzt zurückzuwerfen, denke ich.

    Benutzer72424

    12. August 2011 um 12:27 Uhr

  • Dies ist kein “Slicing” oder zumindest eine harmlose Variante davon. Das eigentliche Problem tritt auf, wenn Sie dies tun B b1; B b2; A& b2_ref = b2; b2 = b1. Sie könnten denken, Sie hätten kopiert b1 zu b2, aber du hast es nicht! Sie haben eine kopiert Teil von b1 zu b2 (der Teil von b1 das B geerbt von A) und verließ die anderen Teile von b2 unverändert. b2 ist jetzt eine frankensteinische Kreatur, die aus ein paar Stückchen besteht b1 gefolgt von einigen Stücken von b2. Pfui! Downvoting, weil ich denke, dass die Antwort sehr irreführend ist.

    – fgp

    22. Januar 2013 um 14:07 Uhr


  • @fgp Dein Kommentar sollte lauten B b1; B b2; A& b2_ref = b2; b2_ref = b1Das eigentliche Problem tritt auf, wenn Sie” … von einer Klasse mit einem nicht virtuellen Zuweisungsoperator ableiten. Ist A sogar zur Ableitung bestimmt? Es hat keine virtuellen Funktionen. Wenn Sie von einem Typ ableiten, müssen Sie sich damit auseinandersetzen, dass dessen Member-Funktionen aufgerufen werden können!

    – Neugieriger

    29. Juni 2013 um 14:54 Uhr

1647315613 126 Was ist Objekt Slicing
fgp

Die meisten Antworten hier erklären nicht, was das eigentliche Problem beim Slicing ist. Sie erklären nur die gutartigen Fälle des Schneidens, nicht die tückischen. Gehen Sie wie bei den anderen Antworten davon aus, dass Sie es mit zwei Klassen zu tun haben A und Bwo B leitet sich (öffentlich) ab A.

In dieser Situation lässt C++ Sie eine Instanz von übergeben B zu A‘s Zuweisungsoperator (und auch an den Kopierkonstruktor). Dies funktioniert, weil eine Instanz von B kann in ein umgewandelt werden const A&was Zuweisungsoperatoren und Kopierkonstruktoren von ihren Argumenten erwarten.

Der gutartige Fall

B b;
A a = b;

Da passiert nichts Schlimmes – Sie haben nach einer Instanz von gefragt A das ist eine Kopie von B, und genau das bekommen Sie. Sicher, a enthält nicht einige von b‘s Mitglieder, aber wie sollte es? Es ist ein Aschließlich nicht a Balso hat es nicht einmal gehört über diese Mitglieder, geschweige denn in der Lage wäre, sie zu speichern.

Der heimtückische Fall

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

Das denken Sie vielleicht b2 wird eine Kopie von sein b1 nachher. Aber leider ist es so nicht! Wenn Sie es untersuchen, werden Sie das feststellen b2 ist eine Frankensteinsche Kreatur, die aus einigen Brocken hergestellt wurde b1 (die Stücke, die B erbt von A) und einige Stücke von b2 (die Stücke, die nur B enthält). Autsch!

Was ist passiert? Nun, C++ behandelt Zuweisungsoperatoren standardmäßig nicht als virtual. Also die Linie a_ref = b1 wird den Zuweisungsoperator von anrufen Anicht das von B. Dies liegt daran, dass für nicht virtuelle Funktionen die erklärt (formal: statisch) Typ (das ist A&) bestimmt, welche Funktion aufgerufen wird, im Gegensatz zu der tatsächlich (formal: dynamisch) Typ (was wäre Bseit a_ref verweist auf eine Instanz von B). Jetzt, ADer Zuweisungsoperator von kennt offensichtlich nur die in deklarierten Mitglieder Asodass nur diese kopiert werden und die Mitglieder hinzugefügt bleiben B unverändert.

Eine Lösung

Die Zuweisung nur an Teile eines Objekts macht normalerweise wenig Sinn, aber C++ bietet leider keine eingebaute Möglichkeit, dies zu verbieten. Du kannst aber auch selbst rollen. Der erste Schritt besteht darin, den Zuweisungsoperator zu erstellen virtuell. Dadurch wird garantiert, dass es immer der tatsächlich Zuweisungsoperator des Typs, der aufgerufen wird, nicht der erklärt Art. Der zweite Schritt ist die Verwendung dynamic_cast um zu überprüfen, ob das zugewiesene Objekt einen kompatiblen Typ hat. Der dritte Schritt besteht darin, die eigentliche Zuweisung in einem (geschützten!) Mitglied vorzunehmen assign()seit B‘S assign() werde wahrscheinlich verwenden wollen A‘S assign() Kopieren As, Mitglieder.

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 aus reiner Bequemlichkeit B‘S operator= überschreibt den Rückgabetyp kovariant, da er weiß dass es eine Instanz von zurückgibt B.

  • IMHO besteht das Problem darin, dass es zwei verschiedene Arten von Substituierbarkeit gibt, die durch Vererbung impliziert werden können: entweder irgendeine derived Wert kann Code gegeben werden, der a erwartet base Wert oder jede abgeleitete Referenz kann als Basisreferenz verwendet werden. Ich wünsche mir eine Sprache mit einem Typsystem, das beide Konzepte separat anspricht. Es gibt viele Fälle, in denen eine Basisreferenz durch eine abgeleitete Referenz ersetzt werden sollte, aber Basisreferenzen nicht durch abgeleitete Instanzen ersetzt werden sollten; Es gibt auch viele Fälle, in denen Instanzen konvertierbar sein sollten, Referenzen jedoch nicht ersetzen sollten.

    – Superkatze

    12. August 2013 um 16:11 Uhr


  • Ich verstehe nicht, was an Ihrem “verräterischen” Fall so schlimm sein soll. Sie haben angegeben, dass Sie: 1) eine Referenz auf ein Objekt der Klasse A erhalten und 2) das Objekt b1 in die Klasse A umwandeln und seinen Inhalt in eine Referenz der Klasse A kopieren möchten. Was hier eigentlich falsch ist, ist die richtige Logik dahinter der angegebene 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, genau wie der Maler es wollte, oder? 🙂

    – Mladen B.

    14. November 2013 um 13:05 Uhr

  • Das Problem ist, anders ausgedrückt, dass C++ standardmäßig von einer sehr starken Art von ausgeht Substituierbarkeit – Es erfordert, dass die Operationen der Basisklasse auf Unterklasseninstanzen korrekt funktionieren. Und das sogar für Operationen, die der Compiler automatisch generiert hat, wie Zuweisungen. Es reicht also nicht aus, die eigenen Operationen in dieser Hinsicht nicht zu vermasseln, man muss auch explizit die vom Compiler erzeugten falschen deaktivieren. Oder halten Sie sich natürlich von öffentlicher Vererbung fern, was normalerweise sowieso ein guter Vorschlag ist 😉

    – fgp

    16. November 2013 um 16:31 Uhr

  • 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

    22. August 2014 um 10:48 Uhr

  • Was zum? Ich hatte keine Ahnung, dass Operatoren als virtuell markiert werden können

    – paulm

    2. März 2015 um 15:23 Uhr

1647315613 889 Was ist Objekt Slicing
Schwarz

Wenn Sie eine Basisklasse haben A und eine abgeleitete Klasse Bdann können Sie Folgendes tun.

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

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

Jetzt die Methode wantAnA benötigt eine Kopie von derived. Allerdings das Objekt derived kann nicht vollständig kopiert werden, da die Klasse B könnte zusätzliche Mitgliedsvariablen erfinden, die nicht in seiner Basisklasse enthalten sind A.

Daher anrufen wantAnA, wird der Compiler alle zusätzlichen Member der abgeleiteten Klasse “abtrennen”. 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 (alle speziellen Verhaltensweisen der Klasse B ist verloren).

  • C++ ist nicht Java! Wenn wantAnA (wie der Name schon sagt!) will ein A, dann kommt es darauf an. Und eine Instanz von Awird, äh, sich wie ein benehmen A. Wieso ist das überraschend?

    – fgp

    22. Januar 2013 um 16:39 Uhr

  • @fgp: Es ist überraschend, weil du bestehe kein A zur Funktion.

    – Schwarz

    3. März 2013 um 7:03 Uhr

  • @fgp: Das Verhalten ist ähnlich. Für den durchschnittlichen C++-Programmierer ist es 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 Slicing überhaupt zu vermeiden, indem (const) Referenzen übergeben werden.

    – Schwarz

    7. April 2013 um 12:13 Uhr

  • @ThomasW Nein, ich würde die Vererbung nicht wegwerfen, sondern Referenzen verwenden. Wenn die Signatur von wantAnA wäre void wantAnA(const A & myA), dann hatte es kein Slicing gegeben. Stattdessen wird eine schreibgeschützte Referenz auf das Objekt des Aufrufers übergeben.

    – Schwarz

    28. Mai 2013 um 7:44 Uhr

  • Das Problem liegt hauptsächlich in der automatischen Umwandlung, die der Compiler durchführt derived zum Typ A. Implizites Casting ist immer eine Quelle für unerwartetes Verhalten in C++, weil es oft schwer zu verstehen ist, wenn man sich den Code lokal ansieht, dass ein Cast stattgefunden hat.

    – pqnet

    6. August 2014 um 23:15 Uhr

Das sind alles gute Antworten. Ich möchte nur ein Ausführungsbeispiel hinzufügen, wenn Objekte als Wert oder als 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'

1647315614 880 Was ist Objekt Slicing
Der Ur-Paul

Dritte Übereinstimmung in Google für “C++ Slicing” gibt mir diesen Wikipedia-Artikel http://en.wikipedia.org/wiki/Object_slicing und dies (erhitzt, aber die ersten Posts definieren das Problem): http://bytes.com/forum/thread163565.html

Also, wenn Sie der Superklasse ein Objekt einer Unterklasse zuweisen. Die Oberklasse weiß nichts von den zusätzlichen Informationen in der Unterklasse und hat keinen Platz, um sie zu speichern, sodass die zusätzlichen Informationen “abgeschnitten” werden.

Wenn diese Links nicht genügend Informationen für eine “gute Antwort” liefern, bearbeiten Sie bitte Ihre Frage, um uns mitzuteilen, wonach Sie noch suchen.

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

Betrachten Sie Klasse A und Klasse B, die von A abgeleitet ist. 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.

Was ist Objekt Slicing
Kartik Maheshwari

In C++ kann ein abgeleitetes Klassenobjekt einem Basisklassenobjekt zugewiesen werden, aber der umgekehrte Weg ist 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
}

Objekt-Slicing findet statt, wenn ein abgeleitetes Klassenobjekt einem Basisklassenobjekt zugewiesen wird, zusätzliche Attribute eines abgeleiteten Klassenobjekts werden abgeschnitten, um das Basisklassenobjekt zu bilden.

1003840cookie-checkWas ist Objekt-Slicing?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy