Sie erinnern sich an Ihre Vorlesungen in Algorithmen, dass es sehr praktisch ist, ein Konzept von Nil
zu haben, dem etwas zugeordnet oder mit dem verglichen werden kann. (Übrigens habe ich nie einen Bachelor in Informatik gemacht.) In Python können wir None
; In Scala gibt es Nothing
(was ein Subobjekt von allem ist, wenn ich es richtig verstehe). Aber meine Frage ist, wie können wir Nil
in C ++ haben? Unten sind meine Gedanken.
Wir könnten ein Singleton-Objekt mit dem Singleton-Entwurfsmuster definieren, aber Mein derzeitiger Eindruck ist, dass die meisten von euch bei dem Gedanken zusammenzucken würden.
Oder wir könnten eine globale oder eine statische definieren.
Mein Problem ist, dass ich in keinem dieser Fälle eine Möglichkeit finde, irgendeine Variable irgendeines Typs Nil
zuzuweisen oder irgendein Objekt irgendeines Typs mit Nil
vergleichen zu können. . Pythons None
ist nützlich, weil Python dynamisch typisiert wird; Scala's Nothing
(nicht zu verwechseln mit Scala Nil
, was eine leere Liste bedeutet) löst das elegant, weil Nothing
ein Unterobjekt von allem ist. Gibt es also eine elegante Möglichkeit, Nil
in C ++ zu haben?
Nein, in C ++ gibt es keinen universellen nil
-Wert. Verbatim, C ++ - Typen sind nicht verwandt und teilen keine gemeinsamen Werte.
Sie können einige -Formen für gemeinsam nutzbare Werte erreichen, indem Sie Vererbung verwenden, aber Sie müssen dies explizit tun, und dies kann nur für benutzerdefinierte Typen erfolgen.
Es gibt zwei verwandte Konzepte, die Sie als Nil
beschreiben: den Einheitentyp und den Optionstyp .
Das ist NoneType
in Python und nullptr_t
in C ++, es ist nur ein spezieller Typ, der einen einzelnen Wert hat, der dieses spezifische Verhalten vermittelt. Da Python dynamisch typisiert ist, kann jedes Objekt mit None
verglichen werden, aber in C ++ können wir das nur für Zeiger tun:
Dies ist semantisch identisch mit Pythons:
%Vor% Python hat (oder braucht) keine solche Funktion, aber alle ML-Familien tun es. Dies ist in C ++ über boost::optional
implementierbar. Dies ist eine typsichere Kapselung der Idee, dass ein bestimmtes Objekt vielleicht einen Wert haben kann oder nicht. Diese Idee ist in der funktionalen Familie ausdrucksvoller als in C ++:
Obwohl es einfach genug ist, um es auch in C ++ zu verstehen, sobald Sie es sehen:
%Vor% Der Vorteil hier ist, dass der Optionstyp ein Werttyp ist und Sie einfach einen "Wert" von nichts ausdrücken können (z. B. kann Ihr optional<int*>
nullptr
als Wert speichern, getrennt von "Nil"). Auch das kann mit jedem Typ funktionieren, nicht nur mit Zeigern - es ist nur, dass Sie für die zusätzliche Funktionalität bezahlen müssen. Ein optional<T>
wird größer sein als ein T
und teurer in der Benutzung sein (wenn auch nur ein bisschen, obwohl das wenig wichtig ist).
Wir können diese Ideen kombinieren, um tatsächlich ein Nil
in C ++ zu machen, mindestens eins, das mit Zeigern und Optionen funktioniert.
(Beachten Sie, dass wir dies sogar auf alles verallgemeinern können, das operator!
, aber es wäre besser, uns auf die klaren Fälle zu beschränken, und ich mag die explizite Aussage, die Ihnen Zeiger und Optionen geben)
Also:
%Vor%C ++ folgt dem Prinzip, dass Sie nicht für das bezahlen, was Sie nicht verwenden. Wenn Sie beispielsweise eine 32-Bit-Ganzzahl speichern möchten, um den gesamten Bereich der 32-Bit-Werte und zu speichern, benötigen Sie mehr als 32 Bit Speicherplatz. Natürlich können Sie clevere Klassen erstellen, um dieses Verhalten darzustellen, aber es ist nicht "out of the box" verfügbar.
Sie können das nicht in C ++ haben, ohne sich auf einen Container verlassen zu müssen, der Ihren Typ einkapselt (zB boost::optional
).
Tatsächlich gibt es eine Kombination von Gründen, denn C ++ ist ein:
None
zuweisen, der nicht zu den verschiedenen Typen gehört, die Sie in anderen Typen darstellen könnten , dynamisch typisierte Sprachen wie Python) None
plus "Alle Werte, die dieser Typ sonst repräsentieren könnte" in einem gemeinsamen Objekt für alle haben deine Typen. [1] die nicht wie integrierte Java-Typen sind, deren Klassen alle von java.lang.Object
geerbt haben.
In den meisten dieser Sprachen sind Variablen nicht als Name von Objekten implementiert, sondern als Griffe für Objekte.
Solche Handles können so eingestellt werden, dass sie "nichts halten" mit sehr wenig zusätzlichem Overhead. Dies ist analog zu einem C ++ - Zeiger, wobei der nullptr
-Zustand "auf nichts zeigt" entspricht.
Oft tun solche Sprachen voll mit der Garbage Collection. Sowohl die Speicherbereinigung als auch die erzwungene indirekte Referenzierung von Daten haben in der Praxis erhebliche Leistungseinbußen zur Folge und sie beschränken die Art von Operationen, die Sie ausführen können.
Wenn Sie eine Variable in solchen Sprachen verwenden, müssen Sie zuerst dem Zeiger folgen, um auf das reale Objekt zuzugreifen.
In C ++ bezieht sich ein Variablenname normalerweise auf das tatsächliche Objekt, nicht auf eine Referenz. Wenn Sie eine Variable in C ++ verwenden, greifen Sie direkt darauf zu. Ein zusätzlicher Zustand (entsprechend "nichts") würde zusätzlichen Aufwand in jeder Variablen erfordern, und eines der Prinzipien von C ++ ist, dass Sie nicht für das bezahlen, was Sie verwenden.
Es gibt Möglichkeiten, einen "Nullable" -Datentyp in C ++ zu erstellen. Diese variieren von der Verwendung von rohen Zeigern, von intelligenten Zeigern oder von etwas wie std::experimantal::optional
. Alle von ihnen haben einen Zustand für "es gibt nichts da", normalerweise erkennbar, indem das Objekt genommen und in einem bool
-Kontext ausgewertet wird. Um auf die tatsächlichen Daten zuzugreifen (vorausgesetzt, dass sie existieren), verwenden Sie unary *
oder ->
.
In C ++ 11 gibt es einen Wert nullptr, der jedem beliebigen Zeigertyp zugewiesen werden kann.
Wenn es kein Zeigertyp ist, muss es da sein. Für bestimmte Werte könnten Sie spezielle "Ich-existiere nicht" Werte definieren, aber es gibt keine universelle Lösung.
Wenn es nicht existiert, sind Ihre Auswahlmöglichkeiten:
boost::optional
für dich bedeutet), oder