Sie dürfen Objektzeiger nicht als Zeiger auf rohe Binärdaten in OOP-Sprachen, einschließlich C ++, behandeln. Objekte sind "mehr als" ihre Repräsentation.
So ist beispielsweise swap
ing zwei Objekte durch Austauschen ihrer Bytes falsch:
Die einzige Situation jedoch, in der ich mir vorstellen kann, dass diese Abkürzung ein Problem verursacht ist, wenn ein Objekt einen Zeiger auf sich selbst enthält, den ich selten (nie?) in der Praxis gesehen habe; es kann aber auch andere Szenarien geben.
Was sind einige reale (reale) Beispiele dafür, wann ein korrektes swap
brechen würde, wenn Sie einen bitweisen Swap durchgeführt hätten?
Ich kann leicht mit erfundenen Beispielen mit Selbstzeigern aufwarten, aber ich kann mir keine echten vorstellen.
Ich werde argumentieren, dass dies fast immer eine schlechte Idee ist, außer in dem speziellen Fall, in dem Profiling durchgeführt wurde und eine offensichtlichere und klarere Implementierung von swap
Leistungsprobleme hat. Selbst in diesem Fall würde ich nur mit dieser Art von Ansatz für geradlinige Strukturen ohne Vererbung gehen, niemals für irgendeine Art von Klasse. Man weiß nie, wann eine Vererbung hinzugefügt wird, die möglicherweise die ganze Sache durchbricht (möglicherweise auch auf wirklich heimtückische Weise).
Wenn Sie eine schnelle Swap-Implementierung wünschen, ist es vielleicht eine bessere Wahl (falls zutreffend), die Klasse zu pimplieren und dann die Implementierung einfach auszutauschen (dies setzt wiederum voraus, dass es keine Backpointer für den Besitzer gibt, die aber leicht enthalten sind) zur Klasse und impl eher als externe Faktoren).
EDIT: Mögliche Probleme mit diesem Ansatz:
Hier geht es nicht speziell um swap
, aber ein Beispiel, das zeigt, dass Low-Level-Optimierungen den Aufwand vielleicht nicht wert sind. Der Compiler findet es oft trotzdem heraus.
Natürlich ist dies mein Lieblingsbeispiel, bei dem der Compiler außergewöhnlich glücklich ist, aber trotzdem sollten wir nicht davon ausgehen, dass Compiler dumm sind und dass wir den generierten Code mit ein paar einfachen Tricks leicht verbessern können.
Mein Testcode ist - konstruiere eine std :: string und kopiere sie.
%Vor%Der erste Konstruktor sieht so aus
%Vor%Der generierte Code ist
%Vor% Hier enthält traits_type::copy
einen Aufruf von memcpy
, der zu einer einzelnen Registerkopie der ganzen Zeichenfolge optimiert ist (sorgfältig passend ausgewählt). Der Compiler transformiert außerdem einen Aufruf in strlen
in eine Kompilierzeit 8
.
Dann kopieren wir es in eine neue Zeichenfolge. Der Kopierkonstruktor sieht so aus
%Vor%und ergibt nur 4 Maschinenanweisungen:
%Vor% Beachten Sie, dass sich das Optimierungsprogramm daran erinnert, dass sich die char
noch im Register rdx
befinden und dass die Stringlänge gleich sein muss, 8
.
Nachdem ich solche Dinge gesehen habe, vertraue ich gerne meinem Compiler und vermeide es, den Code mit etwas Fummelei zu verbessern. Es hilft nicht, es sei denn, das Profiling findet einen unerwarteten Engpass.
(mit MSVC 10 und meiner std :: string-Implementierung)
Warum werden "Selbstzeiger" erfunden?
%Vor%Dieser Typ enthält einen Puffer und eine aktuelle Position im Puffer.
Oder vielleicht haben Sie schon von Iostreams gehört:
%Vor%Wie bereits erwähnt, die kleine String-Optimierung:
%Vor%Dies hat auch einen Selbstzeiger. Wenn Sie zwei kleine Zeichenfolgen konstruieren und diese dann austauschen, entscheiden die Destruktoren, dass die Zeichenfolge "nicht lokal" ist und versuchen, den Speicher zu löschen:
%Vor%Valgrind sagt:
%Vor% Dies zeigt, dass es Typen wie std::streambuf
und std::string
betrifft, kaum erfundene oder esoterische Beispiele.
Grundsätzlich ist bad_swap
nie eine gute Idee, wenn die Typen trivial kopierbar sind, dann ist der Standard std::swap
optimal (von deinem Compiler wird er nicht auf memcpy optimiert, dann bekommst du ihn ein besserer Compiler) und wenn sie nicht trivial kopierbar sind, ist es eine großartige Möglichkeit, Mr. Undefined Behavior und seinen Freund Mr. Serious Bug zu treffen.
Neben den in anderen Antworten erwähnten Beispielen (insbesondere Objekten, die Zeiger auf Teile von sich selbst und Objekte enthalten, die gesperrt werden müssen), könnten auch Zeiger auf das Objekt von einer externen Datenstruktur verwaltet werden, die entsprechend aktualisiert werden muss ( Bitte beachten Sie, dass das Beispiel etwas erfunden ist, um nicht zu übertrieben zu sein (und vielleicht fehlerhaft, weil es nicht getestet wurde):
%Vor% offensichtlich würde so etwas kaputt gehen, wenn Objekte durch memcpy
getauscht werden. Natürlich sind reale Beispiele dafür typischerweise etwas komplexer, aber der Punkt sollte klar sein.
Neben den Beispielen denke ich, dass das Kopieren (oder Swapping) nicht trivial kopierbarer Objekte wie dieses ein undefiniertes Verhalten des Standards ist (könnte das später überprüfen). In diesem Fall würde es überhaupt keine Garantie dafür geben, dass dieser Code mit komplexeren Objekten arbeitet.
Einige nicht bereits erwähnt:
Tags und Links c++ swap bit-manipulation