Sind alle Objekte in C ++ veränderbar, wenn nicht anders angegeben?

8

Ist jedes Objekt in C ++ veränderbar, wenn nicht anders angegeben?

In Python und Javascript kann ich keine Zeichenfolgen, Tupel oder Unicodes ändern. Ich habe mich gefragt, ob es in C ++ etwas gibt, das unveränderlich ist oder jedes Objekt veränderbar ist, und ich muss das Typqualifikationsmerkmal verwenden const, um es unveränderlich zu machen.

GM
quelle
2
Was meinst du mit "standardmäßig"?
B 10овић
Dass alle Objekte in C ++ veränderbar sind, wenn nicht anders angegeben.
GM
1
Ja, sind Sie. Beachten Sie, dass dies nicht mit dem Schlüsselwort "veränderlich" identisch ist.
Olav
3
Die Frage im Fragenkörper ist eine andere Frage als die Frage im Titel?
user253751
@immibis es war nur ein kleines bisschen Wettbewerb hinzuzufügen
GM

Antworten:

18

Die Unveränderlichkeit ist seit einiger Zeit bekannt. Python, Java und C ++ haben unterschiedliche Speichermodelle, die direkte Vergleiche erschweren. Der Autor des Artikels, den Sie ursprünglich zitiert haben , scheint C ++ nicht zu kennen.

Wie in Python, Java und den meisten Sprachen mit mehreren Paradigmen ermöglichen C und C ++ standardmäßig die Veränderbarkeit. Dies ist, was Programmierer normalerweise wollen: Wenn ich eine String xVariable habe, möchte ich in der Lage sein, einen neuen Wert zuzuweisen x = "foo".

Das const-System in C und C ++ ermöglicht eine Menge nuancierter Unveränderlichkeit, die in Python, Java und sogar Scala fehlt. Wenn eine C ++ - Funktion a const std::string&oder a benötigt const char*, verspricht sie (und der Compiler stellt bis zu einem gewissen Grad sicher), dass diese Funktion den Inhalt dieser Zeichenfolge nicht ändert (kann!). Bei einem gegebenen const-Objekt können wir nur Methoden dieses Objekts aufrufen, die auch als const markiert sind. Wenn eine C ++ - Klasse nur öffentliche Mitglieder hat const, ist das Objekt effektiv unveränderlich.

Dies ist jedoch manchmal verwirrend, da in C und C ++ Objekte Speicherorte sind und Variablen Namen für Speicherorte sind. Im Gegensatz dazu sind Variablen in Python und Java Namen für Zeiger auf Objekte. In einer Sprache mit Referenzsemantik x = ybedeutet dies, dass x auf dasselbe Objekt wie y zeigt. Da wir nur Zeiger kopieren, ist dies mit unveränderlichen Objekten möglich. In einer Sprache mit Wertesemantik wie C ++ bedeutet dies "Aktualisieren des Inhalts von xmit dem Inhalt von y". Wenn daher eine Neuzuweisung einer Variablen in C oder C ++ gewünscht wird, hat die Variable möglicherweise keinen const-Typ. Um dies mit unveränderlichen Objekten zu tun, müssten wir explizit einen Zeiger verwenden.

Dass Java und Python unveränderliche Zeichenfolgenobjekte verwenden, ist eine grundlegende Entwurfsentscheidung, hängt jedoch nicht direkt mit den Vorteilen der Unveränderlichkeit in einer Multithreading-Umgebung zusammen. Ein Grund dafür ist, dass Zeichenfolgenliterale im Quellcode zusammengefasst werden können, wodurch die Anzahl der Objekte verringert wird. Dies ist auch in C / C ++ möglich. In C ++ hat das Literal "foo"den Typ const char[4](das 4. Zeichen ist das Beenden '\0'). Ein weiterer Grund ist, dass Einträge in Sets und Schlüssel in Diktaten / Karten nicht geändert werden dürfen. Da Zeichenfolgen häufig als Diktatschlüssel verwendet werden (die meisten Python-Objekte sind Diktatschlüssel), wird durch die Unveränderlichkeit eine häufige Fehlerquelle beseitigt. In Java ist das Java-Sicherheitsmodell ein weiterer Grund für unveränderliche Zeichenfolgen. All diese Gründe haben nichts mit Multithreading zu tun.

Wenn Java mit Blick auf Unveränderlichkeit erstellt worden wäre, hätte die Sprache ganz anders ausgesehen. Obwohl es stark von C ++ inspiriert ist, haben die Designer versucht, eine viel einfachere Sprache zu erstellen. Die Beseitigung von const ist ein solcher Schritt. Das Java-Äquivalent zu einer C ++ - Konstantenreferenz ist ein Adapter oder Dekorator, der alle Mutationsmethoden als implementiert throws new NotImplementedException()und nicht mutierende Methodenaufrufe an die eigentliche Sammlung weiterleitet. Die Tatsache, dass die java.util-Auflistung alle Schnittstellen impliziert, ist ein klares Zeichen dafür, dass sie nicht nach einer unveränderlichen Sprache strebten.

Die Lösung, die Java zur Lösung von Parallelitätsproblemen vorschlug, war nicht Unveränderlichkeit, sondern allgegenwärtiges Sperren. Jedes einzelne Objekt enthält einen Mutex, der für synchronizedBlöcke oder ganze Methoden verwendet werden kann. Wie sich herausstellt, ist das nicht gut für die Leistung, skaliert nicht sehr gut und ist ziemlich fehleranfällig - Sie müssen sich immer noch mit veränderlichen globalen Zuständen auseinandersetzen.

amon
quelle
13
Dies ist, was Programmierer normalerweise wollen => das ist eine ziemlich kontroverse Behauptung. Meine Erfahrung ist eigentlich das Gegenteil: Die meisten lokalen Variablen sind unveränderlich, mit der gelegentlich veränderlichen. Sprachen wie Rust (für die die Veränderlichkeit ein mutSchlüsselwort erfordert und für die der Compiler unnötige Veränderlichkeit kennzeichnet) stützen meine Hypothese: In Rust haben Sie viel mehr letBindungen als Sie let mut.
Matthieu M.
1
@MatthieuM.: Das liegt teilweise daran , dass die Schleifenvariable for x in 1..10nicht let mutdefiniert wird, aber offensichtlich eine gewisse Veränderlichkeit erfordert (nur auf Schleifenebene, nicht innerhalb des Schleifenkörpers).
MSalters
@ MSalters: Natürlich, aber C ++ hat auch eine Range-for-Syntax :)
Matthieu M.
4
@ MSalters Ich würde das als Bindung von 10 Variablen namens x mit 10 Werten charakterisieren, ohne eine zu mutieren
Caleth
1
@MatthieuM. Ich stimme zu, dass Unveränderlichkeit nett ist, aber in C ++ ist es einfach nicht immer eine Option oder zumindest übermäßig umständlich. Zum Beispiel erfordert das Initialisieren einer const-Variablen in mehreren Schritten, dass wir dies in einer separaten Funktion tun, in der dieses Objekt noch nicht const ist. Zumindest in C ++ 11 verwenden wir sofort aufgerufene Lambdas wie const T o = [&]{T o; o.init(...); return o;}();… yay für APIs, die nicht unter Berücksichtigung der Unveränderlichkeit entwickelt wurden.
Amon
9

Fast. Die Sprache selbst behandelt alles als wandelbar es sei denn , Sie verwenden const, aber es ist immer noch möglich , unveränderliche Objekte zu bauen , ohne den Compiler zu sagen sie unveränderlich sind, indem man einfach nicht , einen Weg zu mutieren sie.

Nehmen Sie zum Beispiel diese Klasse:

class MyClass {
    int value;
public:

    MyClass(int v) : value(v) {}
    int getValue() {return value;}

    MyClass &operator=(const MyClass &other) = delete;
};

Instanzen von MyClasssind unveränderlich, weil es keine Möglichkeit gibt, sie zu mutieren - aber nirgends im Code wird tatsächlich angegeben, dass sie unveränderlich sind. (Dies ist die gleiche Art und Weise, wie Instanzen der Java- StringKlasse unveränderlich sind.)

user253751
quelle
(Ich werde rostig) deleteStellt der Kopierzuweisungsoperator hier auch sicher, dass man nicht über die Verschiebungszuweisung op aus einer rWertreferenz zuweisen kann?
underscore_d
Und zählt das Zerstören des Objekts mit einem expliziten Destruktoraufruf und das erneute Erstellen mit neuer Platzierung als "mutierend"?
Peter Green
1
@underscore_d Es zählt als Benutzerdefinition des Operators, um nicht implizit eine Verschiebungszuweisung hinzuzufügen, ja
Caleth
Ich frage mich, ob ein Mechanismus, der den Compiler wissen lässt, dass Objekte einer bestimmten Klasse immer unveränderlich sind, zu einer besseren Optimierung führen könnte. Zum Beispiel wäre es nicht notwendig, eingelöste Objekte zu aktualisieren. In diesem speziellen Beispiel (und im Allgemeinen in Klassen, die diesem Muster folgen) würde es wahrscheinlich helfen, valueconst zu deklarieren und getValue()eine const-Funktion zu deklarieren .
Peter - Wiedereinsetzung Monica
1
@PeterGreen Nein, das zählt als Zerstörung des Objekts und Erstellung eines neuen.
user253751
2

Nein, es gibt eine Ausnahme: Lambdas und die von ihnen erfassten Objekte sind standardmäßig unveränderlich:

int i = 0;

[i]{//capturing the value i
    i++; //does not compile, i is const
};

[i]() mutable{ //make the lambda mutable
    i++; //compiles fine
};

Anstatt also hinzuzufügen const, um es unveränderlich mutablezu machen, fügen Sie hinzu , um es nicht zu machen const.

nwp
quelle