Lebenszeit einer Instanz
-
Hallo, ich möchte die folgende Syntax für eine Funktion unterstützen:
p.add(3) << 1, 2, 3;Ich habe das ganze so gelöst, dass die add-Methode eine Instanz der Klasse 'writer' zurück gibt:
writer whatever::add(byte cid) { return writer(*this, cid); }Und die Klasse 'writer' hat dann die Methoden operator,(int) und operator<<(int) implementiert, die jeweils eine Referenz auf *this zurück geben.
writer& writer::operator<<(int value) { do_something(value); return *this; }Im Destruktor von 'writer' will ich dann abschließend noch irgendwas machen.
writer::~writer() { if(!everything_is_fine()) { throw std::runtime_error("some error"); } do_something_important(); }Nun zu meinen Fragen:
1. warum wir das writer-Objekt bei der Rückgabe von add() nicht kopiert? Ist das return value optimization?
2. ist es garantiert, dass meine writer-Instanz solange existiert, bis das Statement ganz oben abgearbeitet ist?
3. gibt es eine Möglichkeit, das Speichern der Instanz zu verhindern? Obwohl ich bereits den Copy-Konstructor gelöscht habe, kann ich das Ergebnis von add() in eine Variable speichern (wohl durch die move-Semantik?).writer(writer&&) = default; writer(const writer&) = delete; void operator=(const writer&) = delete;
-
2. Ja.
3. Nein.Ganz ehrlich, auch wenn das auf den ersten Blick hübsch wirken mag, solche Sprach-Konstrukte sollte man doch sehr genau überdenken, in den meisten Fällen sind sie nämlich eher unnötig und produzieren auf lange Sicht schlechteren Code. Was spricht gegen add(1, 2, 3)?
-
Schonmal vielen Dank.
cooky451 schrieb:
Was spricht gegen add(1, 2, 3)?
Es muss sich bei den Parametern nicht zwingend um ints handeln, sondern es können float- und int-Werte gemischt vorkommen. Außerdem ist die Anzahl der Parameter dynamisch. Ich habe bereits vorher eine typsichere Variante mit
template<class T...> void add(int, T...)implementiert, die auch nur die für den jeweiligen Fall gewünschte Anzahl an Parametern annimmt, jedoch soll das ganze später mit SWIG aus Python heraus nutzbar gemacht werden, und dann funktioniert Metaprogrammierung natürlich nicht mehr, darum habe ich mich jetzt erstmal für diese Variante entschieden.
Das ganze habe ich so ähnlich auch schonmal hier gesehen: http://eigen.tuxfamily.org/dox/TutorialMatrixClass.html#TutorialMatrixCommaInitializer
-
Oliver Bestmann schrieb:
Im Destruktor von 'writer' will ich dann abschließend noch irgendwas machen.
writer::~writer() { if(!everything_is_fine()) { throw std::runtime_error("some error"); } do_something_important(); }Im Destruktor eine Exception werfen ist eine ganz schlechte Idee. Das soll man nie machen.
Hintergrund ist, dass ein Destruktor möglicherweise zwischen throw und catch aufgerufen werden kann. Wenn da dann noch ein throw kommt, gibt es garantiert Probleme.
-
Oliver Bestmann: Dann nimm die Lösung aus C, die funktioniert auch ohne Templates. http://en.cppreference.com/w/cpp/utility/variadic
Ist wahrscheinlich immer noch sicherer als so ein komisches Writer-Objekt, bei dem man dann immer aufpassen muss es nicht zu kopieren etc.
-
Oliver Bestmann schrieb:
Das ganze habe ich so ähnlich auch schonmal hier gesehen: http://eigen.tuxfamily.org/dox/TutorialMatrixClass.html#TutorialMatrixCommaInitializer
Auch wenn sowas auf den ersten Blick echt cool zu sein scheint. Man hat keinerlei Idee was die Nebeneffekte dieser Überladungen angeht.
Gerade bei diesem Beispiel, die meisten können nicht mal sagen, wo und wann der Kommaoperator vorkommt und wann er aufgerufen wird. Und dass dieser dann auch noch was anderes macht als man denkt, das wird verquer.
-
Ich finde die Syntac so in Ordnung. Insbesondere auch das Beispiel aus eigen.
Es ist so wunderbar intuitiv, fast wie die mathematische Formulierung.Aus
a=(b,c,d)wird
a << b,c,d@cooky ich sehe das Problem bei diesen writer-objekten nicht. Es schreibt ja niemand code wie
foo(a<<b,c,d)
oder
Writer w = (a<<b,c,d);