Pitfalls C++11
-
@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.
-
hustbaer schrieb:
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.
Hast ja recht.
-
Artchi schrieb:
Ein Vorschlag für den IntVector-Designer:
struct Size { std::size_t m_size; Size(std::size_t s) : m_size(s) {}; };
Der Typ ist alleine schon deswegen unpraktisch, weil man ihn nicht als Array-Index verwenden kann. Ausserdem gibts meiner Meinung nach bereits jetzt zu viele Typen für Grössenangaben.
cooky451 schrieb:
Wer den normalen Konstruktor dann mit {} aufruft ist ja mal ganz hart selbst schuld
Wenn die Sprache diese Möglichkeit vorsieht, wird sie mit Sicherheit auch benutzt. Ist ja nicht so, dass die Problematik einem gleich auf Anhieb ins Auge sticht.
Es handelt sich um ein generelles Problem bei neuen Features: Bis sich gängige Idiome und vernünftige Stile in der C++11-Community etablieren, werden einige Jahre ins Land ziehen -- sofern dies überhaupt jemals passiert, zumal wohl viele Leute weiterhin bei C++98 bleiben. Etwas schade finde ich, dass sich bei der Initialisierungssyntax aufgrund der vielen Möglichkeiten Stile bilden müssen, um konsistent zu programmieren. Schöner wäre es, wenn es eine in der Sprache vorgesehene Möglichkeit gäbe; oder selbst bei mehreren Möglichkeiten ein Weg der offiziell empfohlene wäre (überall {} kann nicht sinnvoll sein). Und weil das eben nicht so ist, werden erneut etliche inkompatible Konventionen entstehen.
cooky451 schrieb:
Ok, aber irgendwie auch unrealistisch, aber man nutzt doch nicht plötzlich rvalue Referenzen wenn man eine Kopie haben will.
Warum? Es kann ja sein, dass ich eine Überladung mit
const MyClass&
und eine mitMyClass&&
anbiete, wobei letztere durch Move-Semantik optimiert ist. Verallgemeinere ich das aufconst T&
undT&&
, wird plötzlich nur noch dieT&&
-Überladung aufgerufen, wodurch versehentlich LValues verändert werden können.cooky451 schrieb:
Tjoa in der Tat, allerdings würde ich wegen Übergangsfehlern nicht auf ein Feature verzichten wollen.
Klar, aber es geht ja hier um Pitfalls. Und letztere passieren zum Grossteil beim Übergang zu C++11, weil Entwickler nicht mit neuen Features vertraut sind.
Wobei ich auch sagen muss, dass ich von der Initialisierung in der Klassendefinition noch nicht ganz überzeugt bin... Muss ich mal schauen, wie sich das in der Praxis bewährt.
-
Nexus schrieb:
Es kann ja sein, dass ich eine Überladung mit
const MyClass&
und eine mitMyClass&&
anbieteEben nicht, das war der Punkt. Ich gehe üblicherweise davon aus, dass move Kosten gegen 0 gehen und erwarte Parameter per value*. Dann hat man alles mit einer Klappe geschlagen, indem man einfach aussagt "ich will kopieren". Wenn man also einfach nicht davon ausgeht, dass man bei irgendeiner nonconst Referenz sicher vor Veränderungen wäre, hat man auch kein Problem.
Allerdings stimme ich dir in dem Punkt zu, dass es immer schwieriger wird einen durch die gesamte Community akzeptierten Stil aufzubauen, da keine alten Features aus der Sprache gestrichen werden.
* Wenn ich weiß dass ich kopieren will, gilt natürlich nicht für reine forward Funktionen wie make_unique etc.
-
Ich sehe das Problem mit der {}-Initialisierung nicht wirklich, Unstimmigkeiten gibt es lediglich bei Containerklassen.
Bei Code wieMyComplex c{0, 1};
ist intuitiv klar, was passiert, ohne dass MyComplex irgendeinen Konstruktor mit initializer_list anbieten muss. Entsprechend andere sinnvolle Beispiele lassen sich finden (pair, tuple, alle Arten von Datenstructs, Vektoren/Matrizen, … - eben alles, was Daten speichert, aber nicht wirklich ein Container ist), insofern macht die {}-Syntax für normale Konstruktoren durchaus Sinn.
Bei Containerklassen ist die Lage ähnlich. Bei
MyVector v{0, 1};
ist für mich auch intuitiv logisch, was passiert, nämlich dass der Vector mit den Elementen 0 und 1 initialisiert wird. Wenn das nicht funktioniert, sondern der (Elem, Size)-Konstruktor aufgerufen wird, hat der Designer geschlampt. Ich würde gar nicht erwarten, dass dieser bei einem Container benutzt wird, geschweige denn würde ich Code schreiben, der sich darauf verlässt. Dazu gibt es die altmodische Syntax.
Insofern ist ein Pitfall bei Containerklassen und ähnlichen Typen vorhanden, ich sehe das aber weit weniger dramatisch, als es hier dargestellt wird.