Pitfalls C++11
-
314159265358979 schrieb:
Der Sinn von Expression Templates ist doch Lazy Evaluation, oder etwa nicht?
Ne, der Sinn von Expressiontemplates ist es, Temporaries zu vermeiden.
-
Das kann aber schon mein Compiler, dazu brauche ich keine Expression Templates.
-
314159265358979 schrieb:
Das kann aber schon mein Compiler, dazu brauche ich keine Expression Templates.
Guck Dir an, wie man es wirklich macht. Ich schätze mal, dass zum Beispiel boost.ublas oder spirit da passenden Code bereithalten. Alternativ guck Dir nochmal irgendwelche Idiomseiten am, die Expressiontemplates behandeln.
Typischweise kann man übrigens auch bei Implementierungen die Expressiontemplates benutzen
std::cout << (a + b)
schreiben, und, Geheimtip, es wird dabei sinnigerweise nicht extra ein operator<< für die Operation überladen.
Auch sowas wiea + b + c
dürfte bei Deiner Implementierung schwierig werden.
-
Die Gefahr bei auto + expression templates ist, dass in einem Design, wo expression-Objekte nur als temporäre Objekte gedacht sind, die irgendwelche Verweise auf andere expression-Objekte speichern, man sich ganz schnell ungültige Zeiger/Referenzen einfangen kann, da man mit auto ja die Lebenszeit eines solchen Objekts quasi verlängern kann. Früher musste man den Typ explizit angeben und es gab ggf eine Konvertierung vom Expression-Objekt zum gewünschten Typ. Mit auto kann man nun expression-Objekte direkt halten, was ggf eine sehr schlechte Idee ist (je nach ET Design).
-
Wobei man sich fragt wo da der Fehler ist. Bei auto, oder bei einer Funktion mit einem "falschen" Rückgabewert, die einfach davon ausgeht, dass die Außenwelt schon weiß was zu tun ist.
-
krümelkacker schrieb:
...
Ich verstehe durchaus, was Pi meint, und es mag durchaus Fälle geben, in denen das so implementiert ist (das englische Wikipediabeispiel macht es z.B. so). Jedoch halte ich die Lösung mit temporären Expression-Objekten für eher ungünstig, weil dies auch ohne auto durchaus zu unerwarteten Problemen führen kann. Und seine Aussage, dass die Rückgabe des konkreten Typs des Operanden die Idee hinter Expression Templates ad absurdum führt, ist einfach falsch. Mehr wollte ich eigentlich gar nicht sagen.
-
Ich sehe auch, dass Rvalue-Referenzen gerne mal missverstanden und missbraucht werden. Viel zu oft bekommt eine Funktion eine Rvalue-Referenz als Rückgabetyp verpasst. Manche verstehen einfach nicht, dass eine Rvalue-Referenz auch nur eine Art Referenz ist und dass da keine Compiler-Magie hintersteckt und plötzlich ohne das Zutun des Klassendesigners ein Objekt der Klasse "movebar" macht.
Der größte Fallstrick in diesem Bereich ist wahrscheinlich die Sonderregel zur Template Argument Deduction:
#include <iostream> template<class T> void foo(T const&) { std::cout << "dings!\n"; } template<class T> void foo(T &&) { std::cout << "bums!\n"; } int main() { int i = 23; foo(i); // i ist ein Lvalue foo(i+0); // i+0 ist ein Rvalue }
Was ist wohl die Ausgabe dieses Programms? Wer hier erwartet, dass das Programm irgendwann mal "dings!" ausgibt, liegt nämlich falsch. Im ersten Fall wird für das zweite Template T=int& deduziert und da dann T&&=int& gilt, nimmt die Funktion eine non-const Lvalue-Ref auf int und ist damit ein besser Match (da kein const). Warum gibt's diese Deduktionsregel, die hier zu T=int& führt? Damit "perfect forwarding" funktioniert.
-
Und sonst fällt mir gerade kein "Pitfall" mehr ein.
Aber ein C++98/03 Pitfall waren ja die vom Compiler generierten Kopierkonstruktoren und Zuweisungsoperatoren. Da sind einfach viele Anfänger reingelaufen. Die Sache scheint sich mit C++11 entschärft zu haben, da ja die automatische Erzeugung von diesen Operationen, wenn z.B. ein benutzerdefinierter Destruktor vorhanden ist, deprecated ist und es wahrscheinlich wenigstens zu einer Warnung kommen wird.
-
Typinferenz
Wie angetönt, siehe auch hier.Die "einheitliche" Initialisierung und Initialisiererlisten
Hier hat man meiner Meinung nach systematisch gepfuscht, indem man alles erlaubt und das entstehende Chaos als einheitlich bezeichnet hat. Durch die syntaktische Mehrfachbelegung können Fehlinterpretationen entstehen.class IntVector { public: MyVector(std::size_t size, int value); }; void Function(IntVector x); // Da ich cool bin, verwende ich auch die coole Initialisierung für 3-elementige Vektoren Function({3, 5}); // Ruft Konstruktor MyVector(std::size_t, int) auf // Der MyVector-Entwickler will auch cool sein und rüstet seine Klasse auf: class IntVector { public: MyVector(std::size_t size, int value); MyVector(std::initializer_list<int> values); }; // Gleiche Anwendung Function({3, 5}); // Ruft Konstruktor MyVector(std::initializer_list<int>) auf // -> Code-Break ohne Compilerfehler oder -warnung
RValue-Referenzen und Templates
Sorgt garantiert massiv für Verwirrung, aber ist für Perfect Forwarding notwendig. Der Fallstrick hat echt Potential.void IntFunction(int&& x) { Change(x); // kein Problem, x ist eh temporär } // Ah, wir können das ja verallgemeinern: template <typename T> void TFunction(T&& x) { Change(x); // Problem, x kann irgendwas sein } int lvalue; IntFunction(lvalue); // Compilerfehler, Funktion ändert nicht aus Versehen Wert TFunction(lvalue); // kein Compilerfehler, Parameter wird als int& (nicht int&&) hergeleitet
Move-Semantik
Wobei ich das Verhalten weniger praxisrelevant finde als bei den oberen Beispielen, aber ist mir noch eingefallen (original von Meyers oder so).struct DynamicArray { std::vector<int> vec; std::size_t size; }; DynamicArray x; x.vec.resize(20); x.size = 20; DynamicArray y = std::move(x); // x.size == 20 // x.vec.size() == 0 // Invarianten durch impliziten Move-Konstruktor verletzt
Initialisierung von Membervariablen in Klassendefinition
Ich hoffe, mein Wissen diesbezüglich ist noch aktuell. Falls ja, könnte sowas passieren (gekünsteltes Beispiel, mir fällt gerade kein sinnvolles ein). Sehe ich aber auch nicht als allzu grosse Gefahr.class MyClass { int member; public: MyClass(); }; MyClass::MyClass() : member(72) {} // Code wird auf C++11 portiert, Änderung der Konstruktordefinition wird vergessen class MyClass { int member = 72; public: MyClass(); }; // Später wird anderer Wert eingesetzt, doch Konstruktor schreibt nach wie vor 72 class MyClass { int member = 80; public: MyClass(); };
-
Nexus! Ja, dein Beispiel kann wirklich üble Auswirkung haben.
Aber vielleicht sollte man sich einfach daran gewöhnen Interface-Typen genauer zu definieren.Ein Vorschlag für den IntVector-Designer:
struct Size { std::size_t m_size; Size(std::size_t s) : m_size(s) {}; }; class IntVector { public: IntVector(Size size, int value); IntVector(std::initializer_list<int> values); }; void Function(IntVector x);
Dann gibt es keine Verwechslung mehr.
-
Tachyon schrieb:
314159265358979 schrieb:
Das kann aber schon mein Compiler, dazu brauche ich keine Expression Templates.
Guck Dir an, wie man es wirklich macht. Ich schätze mal, dass zum Beispiel boost.ublas oder spirit da passenden Code bereithalten. Alternativ guck Dir nochmal irgendwelche Idiomseiten am, die Expressiontemplates behandeln.
Typischweise kann man übrigens auch bei Implementierungen die Expressiontemplates benutzen
std::cout << (a + b)
schreiben, und, Geheimtip, es wird dabei sinnigerweise nicht extra ein operator<< für die Operation überladen.
Auch sowas wiea + b + c
dürfte bei Deiner Implementierung schwierig werden.Dann zeich mich mal wie man das Beispiel hier mit Expression-Tempaltes macht, und die Operatoren trotzdem den konkreten Typ zurückgeben lässt:
http://en.wikipedia.org/wiki/Expression_templates(EDIT: und ich meine *nicht* statt
x = alpha * (u-v)
einfachx = multiplication(alpha, subtraction(u, v))
schreiben. Ob ich jetzt "+" oder "addition" schreibe ist ja letztlich egal.)@Pi
So lange was nicht compiliert, und es sich einfach fixen lässt, sehe ich kein wirkliches Problem.
Fix:// auto c = a + b; auto c = evaluate(a + b); std::swap(a, c);
Blöd werden erst Fälle wo man keinen Compiler-Error bekommt, aber falsches Verhalten (oder Code-Bloat auf Grund von tausenden sinnfreien Template-Instanzierungen).
-
Artchi schrieb:
Nexus! Ja, dein Beispiel kann wirklich üble Auswirkung haben.
Aber vielleicht sollte man sich einfach daran gewöhnen Interface-Typen genauer zu definieren.Ein Vorschlag für den IntVector-Designer:
struct Size { std::size_t m_size; Size(std::size_t s) : m_size(s) {}; }; class IntVector { public: IntVector(Size size, int value); IntVector(std::initializer_list<int> values); }; void Function(IntVector x);
Dann gibt es keine Verwechslung mehr.
Juchu, wir haben eine komplizierte Sprache mit viel Glatteis noch komplizierter und rutschiger gemacht!!!
Ne, sorry, ich hoffe echt du meinst das als Scherz.
-
Was ist an einem weiteren Datentyp bitte kompliziert? Das ist einfach nur ein genauerer Typ als es mit size_t versucht wurde.
Wenn dir weitere Typen kopfzerbrechen bereiten, dann ist das ja dein Problem.
-
Artchi schrieb:
Was ist an einem weiteren Datentyp bitte kompliziert? Das ist einfach nur ein genauerer Typ als es mit size_t versucht wurde.
Wenn dir weitere Typen kopfzerbrechen bereiten, dann ist das ja dein Problem.
Was ist daran nicht kompliziert?
Und vor allem: wie "entschärft" es den von Nexus beschriebenen Stolperstein?
-
@hustbaer: lies alle meine Beträge. Ich schrieb schon was zum englischen Wiki-Beitrag.
-
Tachyon schrieb:
Guck Dir an, wie man es wirklich macht. Ich schätze mal, dass zum Beispiel boost.ublas oder spirit da passenden Code bereithalten.
uBLAs als auch sein schneller counterpart Eigen, sowie eigentlich alle modernen Bibliotheken für lineare Algebra geben die Operation zurück, und kein Temporary.
Das hat midnestens zwei Gründe:
1. RVO kriegt nicht alle Kopien entfernt schon bei Ausdrücken wie:
x = a+(b+c)
muss der Compiler ziemlich schlau sein um sich zu merken, dass er die zurückgegebene Kopie von b+c wiederverwenden kann. Du endest also damit, dich bei jedem Ausdruck selbstständig um die Kopien zu kümmern.
2. Es ist klüger, aus a+(b+c) eine Metaoperation zu machen. Die Operation kann direkt so gebastelt werden, dass der Compiler weniger Probleme hat, sie gut zu optimieren.
Typischweise kann man übrigens auch bei Implementierungen die Expressiontemplates benutzen std::cout << (a + b) schreiben, und, Geheimtip, es wird dabei sinnigerweise nicht extra ein operator<< für die Operation überladen.
uBLAS hat meines Wissens nach einen operator<< überladen, und zwar mit ublas::vector_expression<T> als Argument(das ist ihre Basisklasse für vektor-expression templates)
-
Nexus schrieb:
Die "einheitliche" Initialisierung und Initialisiererlisten
Wer den normalen Konstruktor dann mit {} aufruft ist ja mal ganz hart selbst schuld
Nexus schrieb:
RValue-Referenzen und Templates
Ok, aber irgendwie auch unrealistisch, aber man nutzt doch nicht plötzlich rvalue Referenzen wenn man eine Kopie haben will. Also auch nicht in dem ersten int Beispiel. Die nimmt man nur in Konstruktoren/operator =, oder wenn man auch wirklich forwarden will. Insofern sehe ich das Problem auch hier nicht wirklich.
Nexus schrieb:
Move-Semantik
Wenn sich alle auf eine Bedeutung von move einigen würden, wäre die Sache geritzt: http://www.c-plusplus.net/forum/300248
Nexus schrieb:
Initialisierung von Membervariablen in Klassendefinition
Tjoa in der Tat, allerdings würde ich wegen Übergangsfehlern nicht auf ein Feature verzichten wollen.
-
Tachyon schrieb:
@hustbaer: lies alle meine Beträge. Ich schrieb schon was zum englischen Wiki-Beitrag.
Ja OK, hab ich jetzt.
Was ich nicht verstehe, bzw. für extrem ungünstig halte, sind deine Formulierungen hier.Liest sich einfach alles so, als wolltest du sagen dass man Expression-Templates *machen* kann, ohne jemals irgendwo ne Operation zurückzugeben. Und das ist natürlich vollkommener Quatsch.
z.B. die Aussage hier
Und seine Aussage, dass die Rückgabe des konkreten Typs des Operanden die Idee hinter Expression Templates ad absurdum führt, ist einfach falsch.
Entweder hast du da nicht verstanden was Pi gemeint hat (nämlich genau das was ich oben geschrieben habe: irgendwer muss irgendwo ne Operation zurückgeben, denn das ist es was Expression Templates ausmacht). Oder aber ... ja weiss nicht.
Klar muss man nicht Operatoren nicht mit Expression Templates machen. Nur dann haben sie eben auch genau gar nix mit Expression Templates zu tun. Ob sie intern welche verwenden oder nicht ist dann ja vollkommen egal.Naja, Wurst, es gibt wichtigeres
-
cooky451 schrieb:
Wer den normalen Konstruktor dann mit {} aufruft ist ja mal ganz hart selbst schuld
Was hälst du in diesem Kontext von Stroustrup der in all seinen Codebeispielen aggressiv die Konstruktoren durch {}-Notation ersetzt?
-
otze schrieb:
Was hälst du in diesem Kontext von Stroustrup der in all seinen Codebeispielen aggressiv die Konstruktoren durch {}-Notation ersetzt?
Für structs / initializer_lists ist das ok. Andere Beispiele habe ich noch nicht gesehen - und würde es auch sehr komisch finden.