Jemand erwähnte es im IRC als das Schnittproblem.
c++
inheritance
c++-faq
object-slicing
Frankomania
quelle
quelle
A a = b;
a
ist jetzt ein Objekt vom Typ,A
das eine Kopie von hatB::foo
. Es wird ein Fehler sein, es jetzt zurück zu werfen, denke ich.B b1; B b2; A& b2_ref = b2; b2 = b1
. Man könnte denken , Sie kopiert haben ,b1
zub2
, aber Sie haben nicht! Sie haben einen Teil vonb1
tob2
(den Teil von demb1
, der vonB
geerbt wurdeA
) kopiert und die anderen Teile vonb2
unverändert gelassen .b2
ist jetzt eine frankensteinische Kreatur, die aus ein paarb1
Teilen besteht, gefolgt von einigen Stückenb2
. Pfui! Downvoting, weil ich denke, dass die Antwort sehr irreführend ist.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. IstA
sogar 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!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
A
undB
woherB
(öffentlich) stammtA
.In dieser Situation kann C ++ Sie eine Instanz übergeben
B
zuA
‚s Zuweisungsoperator (und auch den Kopierkonstruktors). Dies funktioniert, weil eine Instanz vonB
in a konvertiert werden kann. Diesconst A&
erwarten Zuweisungsoperatoren und Kopierkonstruktoren von ihren Argumenten.Der gutartige Fall
Dort passiert nichts Schlimmes - Sie haben nach einer Instanz gefragt, von
A
der es sich um eine Kopie handeltB
, und genau das erhalten Sie. Sicher,a
wird einigeb
Mitglieder nicht enthalten , aber wie sollte es sein? Es ist einA
immerhin nichtB
, so hat es nicht einmal gehört , über diese Mitglieder, geschweige denn , wäre in der Lage , sie zu speichern.Der tückische Fall
Sie könnten denken, dass
b2
dies eine Kopie vonb1
später sein wird. Aber leider nicht ! Wenn Sie es inspizieren, werden Sie feststellen, dassb2
es sich um eine frankensteinische Kreatur handelt, die aus einigen Stücken vonb1
(den Stücken,B
die von ihnen erben) bestehtA
) und einigen Stückenb2
(der Stücke, die nurB
enthalten) besteht. Autsch!Was ist passiert? Nun, C ++ behandelt Zuweisungsoperatoren standardmäßig nicht als
virtual
. Somita_ref = b1
ruft die Leitung den Zuweisungsoperator von aufA
, nicht den vonB
. Dies liegt daran, dass für nicht virtuelle Funktionen der deklarierte (formal: statische ) Typ (der istA&
) bestimmt, welche Funktion aufgerufen wird, im Gegensatz zum tatsächlichen (formal: dynamischen ) Typ (der wäreB
, da era_ref
auf eine Instanz von verweistB
). . JetztA
weiß der Zuweisungsoperator offensichtlich nur über die in deklarierten Mitglieder BescheidA
, sodass nur diese kopiert werden und die hinzugefügten MitgliederB
unverä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_cast
zu überprüfen, ob das zugewiesene Objekt einen kompatiblen Typ hat. Der dritte Schritt besteht darin, die eigentliche Zuweisung in einem (geschützten!) Mitglied durchzuführenassign()
, daB
'sassign()
wahrscheinlichA
' sassign()
zum Kopieren vonA
Mitgliedern verwenden möchte .Beachten Sie, dass, für die reine Bequemlichkeit
B
‚soperator=
kovariant überschreibt die Art Rückkehr, da er weiß , dass es eine Instanz ist zurückkehrtB
.quelle
derived
kann dem Code, der einenbase
Wert 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.Wenn Sie eine Basisklasse
A
und eine abgeleitete Klasse habenB
, können Sie Folgendes tun.Jetzt benötigt die Methode
wantAnA
eine Kopie vonderived
. Das Objektderived
kann jedoch nicht vollständig kopiert werden, da die KlasseB
zusätzliche Elementvariablen erfinden könnte, die sich nicht in ihrer Basisklasse befindenA
.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, weilA
-Objekt (alles spezielle Verhalten der KlasseB
geht verloren).quelle
wantAnA
(wie der Name schon sagt!) Einen willA
, dann ist es das, was es bekommt. Und eine Instanz vonA
wird sich wie eine verhaltenA
. Wie ist das überraschend?derived
für den Typ ausführtA
. 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.Das sind alles gute Antworten. Ich möchte nur ein Ausführungsbeispiel hinzufügen, wenn Objekte nach Wert und Referenz übergeben werden:
Die Ausgabe ist:
quelle
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.
quelle
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.
quelle
Derived
implizit in konvertiert werden könnenBase
.) Dies widerspricht offensichtlich dem Open-Closed-Prinzip und ist mit einem hohen Wartungsaufwand verbunden.In C ++ kann ein abgeleitetes Klassenobjekt einem Basisklassenobjekt zugewiesen werden, der andere Weg ist jedoch nicht möglich.
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.
quelle
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.
quelle
struct
, Kompatibilität oder andere Unsinnigkeiten, die Ihnen ein zufälliger OOP-Priester gesagt hat.Base
genausizeof(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 istAlso ... 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.
quelle
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
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
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.
quelle
dog
nicht Teil der Klasse istPet
(dasbreed
Datenelement), nicht in die Variable kopiert wirdpet
? Der Code interessiert sichPet
anscheinend nur für die Datenelemente. Schneiden ist definitiv ein "Problem", wenn es unerwünscht ist, aber das sehe ich hier nicht.((Dog *)ptrP)
Ich schlage vor, zu verwendenstatic_cast<Dog*>(ptrP)
Dog::breed
), in keiner Weise ein FEHLER im Zusammenhang mit SLICING ist?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.
quelle
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:
quelle
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:
Da die Kopierfunktionen der Basisklasse nichts über das Abgeleitete wissen, wird nur der Basisteil des Abgeleiteten kopiert. Dies wird üblicherweise als Schneiden bezeichnet.
quelle
quelle
Wenn ein abgeleitetes Klassenobjekt einem Basisklassenobjekt zugewiesen wird, werden zusätzliche Attribute eines abgeleiteten Klassenobjekts vom Basisklassenobjekt abgeschnitten (verworfen).
quelle
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:
Es wird generiert:
quelle
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
. AlsoAction
hat ein einige Mitgliedsvariablen. Der Einfachheit halber haben wir nur 2, astd::string name
und astd::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: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ötigen
Action
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
Action
seinen eigenen Geschmack ab.Er gibt eine Instanz seiner selbst gebrauten Klasse an die weiter,
push_back
aber 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
f
neigen 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
A
s undB
s auf irgendeine Weise abgeleitet werden.quelle