Ich frage mich, welche möglichen Vorteile Copy-on-Write hat. Natürlich erwarte ich keine persönlichen Meinungen, sondern reale praktische Szenarien, in denen dies auf greifbare Weise technisch und praktisch von Vorteil sein kann. Und mit greifbar meine ich etwas mehr, als Ihnen das Schreiben eines &
Zeichens zu ersparen .
Zur Verdeutlichung steht diese Frage im Zusammenhang mit Datentypen, bei denen durch Zuweisung oder Kopierkonstruktion eine implizite flache Kopie erstellt wird. Durch Änderungen wird jedoch eine implizite tiefe Kopie erstellt, und die Änderungen werden anstelle des ursprünglichen Objekts darauf angewendet.
Der Grund, den ich frage, ist, dass ich anscheinend keine Vorteile darin finde, COW als implizites Standardverhalten zu haben. Ich verwende Qt, das COW für viele Datentypen implementiert hat, praktisch alle, denen dynamisch zugeordneter Speicher zugrunde liegt. Aber wie kommt es dem Benutzer wirklich zugute?
Ein Beispiel:
QString s("some text");
QString s1 = s; // now both s and s1 internally use the same resource
qDebug() << s1; // const operation, nothing changes
s1[o] = z; // s1 "detaches" from s, allocates new storage and modifies first character
// s is still "some text"
Was gewinnen wir mit COW in diesem Beispiel?
Wenn wir nur const-Operationen verwenden wollen, s1
ist dies redundant und kann genauso gut verwendet werden s
.
Wenn wir beabsichtigen, den Wert zu ändern, verzögert COW die Ressourcenkopie nur bis zur ersten nicht konstanten Operation, und zwar auf (wenn auch minimale) Kosten für die Erhöhung der Ref-Anzahl für die implizite Freigabe und das Trennen vom gemeinsam genutzten Speicher. Es sieht so aus, als ob der gesamte Aufwand für COW sinnlos ist.
Im Kontext der Parameterübergabe ist dies nicht viel anders. Wenn Sie den Wert nicht ändern möchten, übergeben Sie ihn als const-Referenz. Wenn Sie ihn ändern möchten, erstellen Sie entweder eine implizite tiefe Kopie, wenn Sie ihn nicht ändern möchten das ursprüngliche Objekt oder übergeben Sie es als Referenz, wenn Sie es ändern möchten. Wiederum scheint COW unnötiger Overhead zu sein, der nichts bewirkt, und fügt nur eine Einschränkung hinzu, dass Sie den ursprünglichen Wert nicht ändern können, selbst wenn Sie möchten, da sich jede Änderung vom ursprünglichen Objekt löst.
Je nachdem, ob Sie über COW Bescheid wissen oder sich dessen nicht bewusst sind, kann dies entweder zu Code mit unklarer Absicht und unnötigem Overhead führen oder zu einem völlig verwirrenden Verhalten, das nicht den Erwartungen entspricht und Sie am Kopf kratzen lässt.
Mir scheint, dass es effizientere und lesbarere Lösungen gibt, unabhängig davon, ob Sie eine unnötig tiefe Kopie vermeiden möchten oder eine erstellen möchten. Wo liegt also der praktische Nutzen von COW? Ich gehe davon aus, dass es einen gewissen Nutzen geben muss, da es in einem so populären und mächtigen Rahmen verwendet wird.
Darüber hinaus ist COW nach dem, was ich gelesen habe, jetzt in der C ++ - Standardbibliothek ausdrücklich verboten. Ich weiß nicht, ob die Nachteile, die ich darin sehe, etwas damit zu tun haben, aber so oder so muss es einen Grund dafür geben.
[]
Operator verwenden. COW ermöglicht also schlechtes Design - das klingt nicht nach einem großen Vorteil :) Der Punkt im letzten Absatz scheint gültig zu sein, aber ich selbst bin kein großer Fan impliziten Verhaltens - die Leute halten es für selbstverständlich und haben es dann Es ist schwierig herauszufinden, warum Code nicht wie erwartet funktioniert, und sich immer wieder zu fragen, bis sie herausgefunden haben, was sich hinter dem impliziten Verhalten verbirgt.const_cast
betrifft, so scheint es, als könne es die Kuh genauso leicht brechen wie das Passieren durch konstante Referenz. Wenn Sie beispielsweiseQString::constData()
a zurückgebenconst QChar *
-const_cast
das und COW zusammenbrechen -, werden die Daten des Originalobjekts mutiert.char*
offensichtlich nicht bekannt). Was das implizite Verhalten betrifft, denke ich, dass Sie Recht haben, es gibt Probleme damit. Das API-Design ist ein konstantes Gleichgewicht zwischen den beiden Extremen. Zu implizit, und die Leute verlassen sich auf spezielles Verhalten, als ob es de facto Teil der Spezifikation wäre. Zu explizit und die API wird zu unhandlich, wenn Sie zu viele zugrunde liegende Details offenlegen, die nicht wirklich wichtig waren und plötzlich in Ihre API-Spezifikation geschrieben werden.string
Klassen haben COW-Verhalten, weil die Compiler-Designer bemerkt haben, dass ein großer Teil des Codes Zeichenfolgen kopiert, anstatt const-reference zu verwenden. Wenn sie COW hinzufügen, könnten sie diesen Fall optimieren und mehr Menschen glücklich machen (und es war legal, bis C ++ 11). Ich schätze ihre Position: Während ich meine Zeichenfolgen immer als konstante Referenz übergebe, habe ich all diesen syntaktischen Müll gesehen, der die Lesbarkeit nur beeinträchtigt. Ich hasse es zu schreiben,const std::shared_ptr<const std::string>&
nur um die richtige Semantik zu erfassen!Für Strings und dergleichen scheint es, als würde es häufigere Anwendungsfälle pessimieren als nicht, da der übliche Fall für Strings häufig kleine Strings sind und dort der Aufwand für COW die Kosten für das einfache Kopieren des kleinen Strings bei weitem überwiegt. Eine kleine Pufferoptimierung ist für mich dort viel sinnvoller, um in solchen Fällen die Heap-Zuordnung anstelle der String-Kopien zu vermeiden.
Wenn Sie jedoch ein schwereres Objekt wie ein Android-Gerät haben und es kopieren und nur seinen kybernetischen Arm ersetzen möchten, erscheint COW durchaus sinnvoll, um eine veränderbare Syntax beizubehalten und gleichzeitig zu vermeiden, dass das gesamte Android-Dokument nur tief kopiert werden muss Geben Sie der Kopie einen einzigartigen Arm. Es mag überlegen sein, es zu diesem Zeitpunkt nur als persistente Datenstruktur unveränderlich zu machen, aber eine "partielle Kuh", die auf einzelne Android-Teile angewendet wird, erscheint in diesen Fällen vernünftig.
In einem solchen Fall würden sich die beiden Kopien des Android den gleichen Oberkörper, die gleichen Beine, Füße, den gleichen Kopf, den gleichen Hals, die gleichen Schultern, das gleiche Becken usw. teilen. Die einzigen Daten, die zwischen ihnen unterschiedlich und nicht geteilt wären, sind der Arm, der hergestellt wurde Einzigartig für den zweiten Android beim Überschreiben seines Armes.
quelle
std::vector<std::string>
zuvoremplace_back
und verschieben Semantik in C ++ 11) . Grundsätzlich verwenden wir aber auch Instanzen. Das Knotensystem kann die Daten ändern oder nicht. Wir haben Dinge wie Pass-Through-Knoten, die nichts mit der Eingabe tun, sondern nur eine Kopie ausgeben (sie sind für die Benutzerorganisation seines Programms da). In diesen Fällen werden alle Daten für komplexe Typen flach kopiert ...A
kopiert wird und nichts mit dem Objekt gemacht wirdB
, ist es eine billige flache Kopie für komplexe Datentypen wie Netze. Wenn wir jetzt Änderungen vornehmenB
, werden die Daten, die wir ändern,B
durch COW eindeutig,A
bleiben jedoch unberührt (mit Ausnahme einiger atomarer Referenzzählungen).