Resource icon

[C++] Konstruktoren richtig schreiben

-AB-

Well-Known Member
c-b Team
c-b Experte
#1
-AB- hat eine neue Ressource erstellt:

[C++] Konstruktoren richtig schreiben - Jeder schreibt sie - aber wie schreibt man sie richtig?

Wer objektorientiert coded, hat sicher schon viele Konstruktoren geschrieben. Wer das RAII verwendet, nutzt Konstruktoren und Destruktoren, um Operationen an die Lebenszeit von Objekten zu binden.

Aber zunächst - was ist anders in C++, so dass man einen Konstruktor überhaupt "falsch" schreiben kann?

1) Was man oft sieht: Der "Java-Style" - Konstruktor
Nehmen wir eine einfache geometrische Vector-Klasse für 2D an, mit 2 Membervariablen x und y. In Java würde man die Klasse und den...
Weitere Informationen zu dieser Ressource...
 

German

Well-Known Member
c-b Experte
#2
Hallo -AB-
...wenn ich nach der Initialisierung Werte prüfen / ausgeben möchte?
Nun, dann haben wir den legitimen Fall, für den der Codeblock des Konstruktors gedacht ist. :)
Meinst du mit Codeblock den Rumpf des Konstruktors?
Also, ist es legitim Werte aus der Initialisierungsliste bereits im Rumpf zu prüfen?:
C++:
myClass::myClass()
  : myVar(myFunction())
{
  if (!myVar)
  {
    // error handling here
  }
}
 

-AB-

Well-Known Member
c-b Team
c-b Experte
#3
Klar. So kannst du sicher gehen, dass du immer ein valides Objekt konstruiert hast. Wobei das Error-Handling in den meisten Fällen wohl eine Exception wäre - oder ein komplizierter retry-Mechanismus, der natürlich auch wieder mit einer Exception failen kann.

Nachteil ist, dass du myVar so aussuchen musst, dass ein Extra Fehlerwert gespeichert werden kann. Wobei diese Information *nach* dem Konstruktor völlig egal ist, da du ja nur valide Objekte ablegst.

Idealerweise könntest du auf Fehler schon vorher prüfen, in etwa indem du eine Methode aufrufst, die mit Sicherheit ein valides Ergebnis returned, oder aber eine Exception wirft. Das kann aber unmöglich sein, wenn es etwa API-Methoden sind - oder würde erfordern, alle Methoden einzeln zu wrappen (und dann liegt die Erkennung, welcher Wert valide ist und welcher nicht, in den Methoden, und nicht mehr im Konstruktor, was eventuell nicht wünschenswert ist.)

Alternativ kann man auch - ein bisschen spooky - in der Memberinitialisierungsliste schon den ternären if-Operator verwenden:

C++:
int* tryGetInt()
{
    return nullptr; //always "fails"
}

struct test
{
    test() : myInt(tryGetInt() != nullptr ? *(tryGetInt()) : throw std::runtime_error("fail"))
    {}

    const int myInt;
};
In dem Fall erspart man sich, einen Member zu halten der invalide sein kann. Bei einem unsigned int - welcher Wert steht für invalid? Sind nicht alle prinzipiell möglich? (Gut, dafür gibt es ja auch Dinge wie boost::optional, aber.... Lesbarkeit, Objektgröße und Aufrufskosten leiden darunter)
Natürlich leidet auch die Lesbarkeit wenn man x Member in der Initialisierungsliste per ?-Operator alle testet - aber dann ist eventuell doch schon wieder das Design nicht optimal ;)
 

German

Well-Known Member
c-b Experte
#4
Bei mir sind es da hauptsächlich wieder WinAPI Funktionen, die bspw. ein Handle zurück geben, auf die meine Frage gezielt hat. Die schlagen zwar so selten fehl, dass es mir selbst noch nie untergekommen ist, trotzdem frage ich immer den Fehlerfall ab. Bislang habe ich das erst in den Methoden gemacht, ist aber eigentlich unsinnig. Erzeugt wieder zig Coderedundanzen, die man sich sparen kann, wenn man gleich einmalig im Konstruktor prüft und es macht auch keinen Sinn einen invaliden Wert weiter zu halten.
Der Rest (ternäre Operationen & Co.) ist mir geläufig. Mit Exceptions habe ich bislang kaum gearbeitet, bisher konnte ich auch immer recht gut das Fehlschlagen in den Rückgabewert packen. Bei einem unsigned ist es am Ende auch nur eine Plausibilitäts- bzw. Definitionsfrage.
 
Oben