C++Guns – RoboBlog

10.03.2017

C++17 Guns - std::optional Part II

Filed under: Allgemein — Tags: — Thomas @ 11:03

Wie schon im ersten Teil berichtet, suche ich vernünftige Anwendungsfälle für std::optional.
Dabei ist mir aufgefallen, dass schon viele Variablen implizit optional sind/behandelt werden. Ohne, dass eine extra Variable wie z.B. bool vorhanden; vorhanden ist. Nehmen wir z.B. ein Adressbuch. Wenn der Adresszusatz nicht angegeben wurde, ist der String einfach leer. Ein std::optional<std::string>> adresszusatz; wäre redundante Information. Einmal explizit im std::optional und einmal implizit im string::size.

Bei Strings ist das einfach. Doch was ist mit Zahlen? Wenn die Hausnummer in der Adresse als Integer gespeichert wäre, und es fehlt die Information, welche Zahl ist in der Variablen hausnummer dann gespeichert? int hausnummer = 0; Null als Default? Oder lieber -1? Oder Undefiniert? Oder MAX/MIN Integer Wert? Bei floats gäbe es NaN...

Die Idee ist, dass Zahlen einen validen Wertebereich haben. Negative Hausnummern gibt es einfach nicht. Auch ist es sehr unwahrscheinlich, dass eine Straße zwei Milliarden Häuser hat. Die Information, ob der Wert vorhanden ist, oder nicht, ist im Wert selbst kodiert. Das widerspricht aber dem C++ Ziel, eine generalisierte Sprache zu sein. Die allgemeingültige Lösung für std::optional kann daher nicht so implementiert sein, dass die 'vorhanden' oder 'nicht vorhanden' Information in der eigentlichen Variablen mit gespeichert wird.

Wir würde eine Implementation von "optional" aussehen?
Eine zusätzliche boolean Variable bool vorhanden = false; wäre denkbar. Aber das kleine bool verhunzt das Speicherlayout der Klasse. Man könnte nur einen Pointer nehmen. Ein nullptr bedeutet, dass der Wert nicht vorhanden ist. Aber dann haben wir das owning Problem wie mit dem std::shared_ptr auch. Was ist, wenn std::optional eine Kopie der Variable hält? Problem 1 gelöst. Problem 2: Welcher Wert hat denn das optional, wenn es kein Wert gibt? Ein default Wert. Welcher? Nicht jede Klasse hat einen default ctor oder kann überhaupt einen haben.

Ich sehe, default Werte, default ctors und std::optional und string::size (vector::size) haben viel gemeinsam. Da kommt auch gleich die RAII (Ressourcenbelegung ist Initialisierung) Idee daher. Man schreibt nicht einfach std::string s; int i; oder int i=0; Sondern belegt die Variable gleich mit einem gültigen Wert.
std::string s = func(); int i = func(); Was aber, wenn func() keinen Wert zurück liefern kann, weil es keinen gibt? Das muss nicht gleich ein Fehler sein. Datensätze sind nunmal nie vollständig. Das ist das selbe Problem wie oben besprochen.

Ich persönlich fühle mich nicht wohl mit dem Gedanken, überall im Code optionals zu haben, die existieren könnten, oder nicht. Die Variationen gehen exponentiell mit der Anzahl der Variablen. Das will niemand und ich bezweifle, dass man das überhaupt braucht.

Also mir kommen da zwei Möglichkeiten in den Sinn. Der Erste, wie oben schon angedeutet, einen gültigen Wertebereich definieren. Viele Variablen bilden ja irgendwas aus der Realität ab. Z.B. eine Höhenangabe auf der Welt. Was ist der höchste Punkt? Irgendwas mit 8000m. Was ist der tiefste Punkt? Irgendwas mit -11000m. Man kann also eine noDepthValue definieren mit -99999m und ihn als default Wert für Höhenangaben nutzen. Das lässt sich mit dem Typesystem auch leicht implementieren. using Height_t = double; Oder beliebig kompliziert. Das Funktioniert mit Koordinaten genauso. Im sphärischen System gibt es keinen Rechtswert größer als 180grad. Vorraussetzung dafür ist, dass das "Ding" in der Realität auch genau diesen Typ in C++ bekommt. Also kein double x; mehr. Lieber Coordinate x; So können auch gleich SI Einheiten mit einbezogen werden. Aber das ist ein anderes Thema. Das ist auch gleich Performant und SSE freut sich, wenn die Daten alignt rein kommen.

Ist die erste Möglichkeit schon sehr Domänenspeziell, brauchen wir auch eine allgemeingültige C++ Philosophie Lösung. Hier orientiere ich mich an std::string::size bzw std::vector::size. Wie schon oben Erwähnt, ist die Information, ob ein String leer ist oder nicht, in der size Variable in der std::string Klasse abgelegt. (Und nicht, wie bei C, im den eigentlichen String Daten selbst). Was hindert mich daran, das für alle anderen Daten auch zu tun? Für das Beispiel nehme ich nochmal die Adresse.


struct Name {
  string vorname, nachname;
};
struct Adresse {
  vector<Name> name;
  vector<string> strasse;
  vector<int> hausnummer;
  vector<string> stadt;
};

Na, ist das nichts? ;) Ich finde das sehr realistisch. Hat die Adresse keinen hausnummer, so ist der vector einfach leer. Ein leerer Vector führt bei keiner Library zu Problemen. Jede for() Schleife ist glücklich damit. Da steckt das if(vorhanden) schon drin. Und wer sagt denn bitte schön, dass eine Person nur einen Namen haben darf? Wenn man heiratet, ändert sich der Nachname. Wenn man die Identität wechselt, ändert sich der Name. Die Freunde nennen einem meist nicht beim vollen Namen. Im Internet hat man Nicks. Man kann sich auch als jemand anderes ausgeben. In anderen Ländern wird sein Name womöglich leicht anders geschrieben und ausgesprochen. Soll ich weiter machen? Das selbe gilt natürlich auch für die restlichen Variablen. Hier wäre also sogar ein Zeitfaktor mit implementiert ;)
Absolut übertrieben und Anwendungsfern? Ja aber, genau das wollen wir doch mit c++.

No Comments »

No comments yet.

RSS feed for comments on this post.

Leave a comment

You must be logged in to post a comment.

Powered by WordPress