911 Stimmen

Was ist Object Slicing?

Was ist Objektslicing in C++ und wann kommt es vor?

730voto

David Dibben Punkte 17424

Beim "Slicing" wird ein Objekt einer abgeleiteten Klasse einer Instanz einer Basisklasse zugewiesen, wodurch ein Teil der Informationen verloren geht - ein Teil davon wird "weggeschnitten".

Zum Beispiel,

class A {
   int foo;
};

class B : public A {
   int bar;
};

So kann ein Objekt vom Typ B hat zwei Datenelemente, foo y bar .

Wenn Sie dies also schreiben würden:

B b;

A a = b;

Dann werden die Informationen in b über das Mitglied bar ist verloren in a .

73 Stimmen

Sehr informativ, aber siehe stackoverflow.com/questions/274626#274636 für ein Beispiel, wie Slicing bei Methodenaufrufen auftritt (das die Gefahr etwas besser verdeutlicht als das einfache Zuweisungsbeispiel).

68 Stimmen

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 per Referenz übergeben habe. Das zeigt, wie gute Gewohnheiten einem helfen können.

0 Stimmen

@David : können Sie Ihre Linie im Detail erklären Then the information in b about member bar is lost in a. . werden die Daten von foo nach der Zuweisung beschädigt? aber warum?

624voto

fgp Punkte 7628

In den meisten Antworten wird nicht erklärt, was das eigentliche Problem beim Slicing ist. Sie erklären nur die harmlosen Fälle von Slicing, nicht aber die tückischen. Gehen Sie, wie die anderen Antworten, davon aus, dass Sie es mit zwei Klassen zu tun haben A y B , wobei B leitet sich (öffentlich) ab von A .

In dieser Situation können Sie in C++ eine Instanz von B zu A Zuweisungsoperator (und auch zum Kopierkonstruktor). Dies funktioniert, weil eine Instanz von B umgewandelt werden in eine const A& Das ist das, was Zuweisungsoperatoren und Kopierkonstrukteure von ihren Argumenten erwarten.

Der gutartige Fall

B b;
A a = b;

Dort passiert nichts Schlimmes - Sie haben nach einer Instanz von A die eine Kopie ist von B und das ist genau das, was Sie bekommen. Klar, a enthält nicht einige der b Mitglieder, aber wie soll das gehen? Es ist ein A schließlich nicht ein B also hat es nicht einmal gehört über diese Mitglieder, geschweige denn in der Lage sein, sie zu speichern.

Der verräterische Fall

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

Man könnte meinen, dass b2 wird eine Kopie sein von b1 nachher. Aber, leider, es ist no ! Wenn Sie es untersuchen, werden Sie feststellen, dass b2 ist eine Frankenstein'sche Kreatur, die aus einigen Stücken von b1 (die Klumpen, die B erbt von A ), und einige Brocken von b2 (die Chunks, die nur B enthält). Autsch!

Was ist passiert? Nun, C++ behandelt standardmäßig Zuweisungsoperatoren nicht als virtual . Somit ist die Linie a_ref = b1 ruft den Zuweisungsoperator von A , nicht die von B . Dies liegt daran, dass bei nicht-virtuellen Funktionen die erklärt (förmlich: statisch ) Typ (das ist A& ) bestimmt, welche Funktion aufgerufen wird, im Gegensatz zur aktuell (förmlich: dynamisch ) Typ (das wäre dann B ya que a_ref verweist auf eine Instanz von B ). Jetzt, A kennt der Zuweisungsoperator offensichtlich nur die Mitglieder, die in A kopiert, so dass nur diese kopiert werden, während die Mitglieder, die in B unverändert.

Eine Lösung

Die Zuweisung nur an Teile eines Objekts macht in der Regel wenig Sinn, doch bietet C++ leider keine eingebaute Möglichkeit, dies zu verbieten. Sie können jedoch Ihre eigene Methode entwickeln. Der erste Schritt besteht darin, den Zuweisungsoperator virtuell . Damit ist gewährleistet, dass es immer die aktuell Zuweisungsoperator des Typs, der aufgerufen wird, und nicht der erklärt Typs. Der zweite Schritt ist die Verwendung von 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!) Member vorzunehmen assign() ya que B 's assign() werden wahrscheinlich verwenden wollen A 's assign() zum Kopieren A 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
  }
};

Der Einfachheit halber sei darauf hingewiesen, B 's operator= kovariant den Rückgabetyp außer Kraft setzt, da es kennt dass es eine Instanz von B .

1 Stimmen

Dann werden einige Operationen an einem Objekt von A sind nicht erlaubt, wenn das Objekt vom Typ B .

17 Stimmen

IMHO besteht das Problem darin, dass es zwei verschiedene Arten von Substituierbarkeit gibt, die durch Vererbung impliziert werden können: entweder jede derived Wert kann an Code gegeben werden, der eine base Wert oder eine abgeleitete Referenz kann 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 ersetzbar sein sollte, aber abgeleitete Instanzen sollten nicht durch Basisreferenzen ersetzbar sein; es gibt auch viele Fälle, in denen Instanzen konvertierbar sein sollten, aber Referenzen nicht ersetzt werden sollten.

1 Stimmen

Wenn eine Funktion in .NET konzeptionell KeyValuePair<SiameseCat, ToyotaPrius> sollte man dieses Ergebnis in einem Speicherort des Typs KeyValuePair<Animal, Vehicle> nicht deshalb, weil eine Instanz von Ersterem eine Instanz von Letzterem ist, sondern weil die Interpretation des Rückgabewerts als KVP<A,V> würde sie effektiv in eine solche verwandeln. Leider würde dies eine von der normalen Vererbungshierarchie getrennte Hierarchie erfordern, da eine verpackte Instanz des ersten Typs definitiv nicht mit einer verpackten Instanz des zweiten Typs gleichzusetzen ist.

180voto

Black Punkte 4870

Wenn Sie eine Basisklasse haben A und eine abgeleitete Klasse B dann 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 ist der Gegenstand derived kann nicht vollständig kopiert werden, da die Klasse B könnte zusätzliche Mitgliedsvariablen erfinden, die nicht in ihrer Basisklasse enthalten sind A .

Daher ist die Aufforderung wantAnA wird der Compiler alle zusätzlichen Mitglieder der abgeleiteten Klasse "abschneiden". Das Ergebnis könnte ein Objekt sein, das Sie nicht erstellen wollten, weil

  • sie kann unvollständig sein,
  • verhält es sich wie ein A -Objekt (alle speziellen Verhaltensweisen der Klasse B verloren geht).

56 Stimmen

C++ ist no Java! Wenn wantAnA (wie der Name schon sagt!) will eine A dann ist es das, was er bekommt. Und eine Instanz von A wird sich wie ein A . Wieso ist das überraschend?

108 Stimmen

@fgp: Es ist überraschend, denn Sie kein A bestehen zur Funktion.

4 Stimmen

@Schwarz: Aber wantAnA hat es gesagt will eine Eins, und das ist es, was sie bekommt. Es ist dasselbe, als würde man eine Funktion deklarieren, die einen int annimmt, 0,1 übergibt und sich dann beschwert, dass die Funktion 0 erhält...

57voto

geh Punkte 726

Das sind alles gute Antworten. Ich möchte nur ein Ausführungsbeispiel für die Übergabe von Objekten als Wert und nicht als Referenz hinzufügen:

#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'

0 Stimmen

Hallo. Tolle Antwort, aber ich habe eine Frage. Wenn ich etwas wie dieses ** dev d; base* b = &d;** Das Slicing findet auch statt?

1 Stimmen

@Adrian Wenn Sie in der abgeleiteten Klasse neue Mitgliedsfunktionen oder Mitgliedsvariablen einführen, sind diese nicht direkt über den Zeiger der Basisklasse zugänglich. Sie können jedoch von den überladenen virtuellen Funktionen der Basisklasse aus auf sie zugreifen. Siehe dies: godbolt.org/z/LABx33

36voto

The Archetypal Paul Punkte 40239

Der dritte Treffer bei Google für "C++ Slicing" ergibt diesen Wikipedia-Artikel http://en.wikipedia.org/wiki/Object_slicing und dies (erhitzt, aber die ersten paar Beiträge definieren das Problem) : http://bytes.com/forum/thread163565.html

Wenn Sie also ein Objekt einer Unterklasse der Oberklasse zuordnen. Die Oberklasse weiß nichts von den zusätzlichen Informationen in der Unterklasse und hat keinen Platz, um sie zu speichern, also werden die zusätzlichen Informationen "abgeschnitten".

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

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X