vector struct push_back -> wie?
-
Don Carsto schrieb:
Du meine Güte, so einfach, ich dachte, sowas hätte ich auch ausprobiert. Jedenfalls funktioniert das nun wunderbar.
Vielen vielen Dank...
Gruß,
DCEs sieht auf den ersten Blick einfach aus, aber die STL ist tükkisch, wenn man eins nicht beachtet. Und jetzt kommts: Das Grundprinzip der STL ist KOPIEREN. Alles, was in einen std::vector eingefügt oder heraus geholt wird, wird kopiert! Im Fall von std::string ist das aus Performancesicht reiner Wahnsinn. Die höchste Effiziens erreicht man durch halten von Pointern und damit sind auch keine SmartPointer gemeint.
Also statt einem
std::vector<std::string> str_array;
lieber ein
std::vector<std::string *> str_array;
Einziges Manko... das Leerräumen eines std::vector fällt nun in die Zuständigkeit des Entwicklers.
statt
str_array.clear();
muss man etwas mehr machen
// Typedefs sind dein Freund (jeder Ada Entwickler weiß ganz genau, was ich damit meine) typedef std::vector<std::string *> StrVec; typedef std::string Str; StrVec str_array; // Strings einfügen str_array.push_back(new Str("Test 1")); str_array.push_back(new Str("Test 2")); str_array.push_back(new Str("noch mehr Tests")); // str_array aufräumen for (StrVec::iterator it = str_array.begin(); it < str_array.end(); ++it) delete *it; str_array.clear(); // noch effizienter aufräumen (bei vielen Elementen) void del(Str *ptr) { delete ptr; } std::for_each(str_array.begin(), str_array.end(), del); // alle ausgeben void print(Str *ptr) { std::cout << *ptr << std::endl; } std::for_each(str_array.begin(), str_array.end(), print);
Zugegeben, es sieht etwas ungewohnt aus, aber andererseits hat jeder, der behauptet C sei eine Submenge von C++ oder C++ ist nur erweitertes C, absolut keine Ahnung.
Das letzte Beispiel ist jedenfalls ein halbwegs vernünftiger C++ Stil.
-
Noch so einer, der meint, man müsste mit Zeigern in C++ hantieren - omg!
-
Th69 schrieb:
Noch so einer, der meint, man müsste mit Zeigern in C++ hantieren - omg!
Zugegeben, man könnte bei den Functors für for_each auch Referenzen verwendet, bei Containern geht das aber nicht. Das Problem ist, dass eine Referenz weder ein Zeiger, noch ein eigener Wert ist, auch wenn es intern (wenn man sich mal die Assemblerausgabe anschaut) als Pointer gehandhabt wird, so wird im Gegensatz zum Pointer keine Ownership verteilt. Spätestens bei Vererbung wird klar, warum man in Containern keine SmartPointer oder Objekte halten sollte...
Tipp: Scott Meyers Effective STL Item 7/8
Ich will nicht ettliche Seiten zitieren, aber er bringt es exakt auf den Punkt.
PS: Ich sehe es im Prinzip auch so wie du. Referenzen sind die C++ Antwort auf die C Pointer. Der erste C++ Entwurf sah sogar eine Abschaffung von C Pointern vor... vielleicht hätte man das tatsächlich tun sollen. Das fatale sind nicht die Pointer, sondern die Pointerarithmetik.
-
Akiko schrieb:
Die höchste Effiziens erreicht man durch halten von Pointern und damit sind auch keine SmartPointer gemeint.
Also statt einem
std::vector<std::string> str_array;
lieber ein
std::vector<std::string *> str_array;
Wurde ja wieder mal Zeit, dass jemand so einen Unsinn schreibt.
- Für jedes einzelne Element Speicher anzufordern und wieder freizugeben erfordert einen ziemlichen Geschwindigkeits- und Speicheroverhead. Gerade da
std::string
wahrscheinlich selbst eher klein ist und nochmals Zeiger auf dynamischen Speicher hält, lohnt sich das nicht unbedingt. - Mit manueller Speicherverwaltung erledigst du die Aufgabe, die dir die STL-Container abnehmen sollten, wieder von Hand. Dabei nimmst du sämtliche Nachteile in Kauf (von denen dir einige wahrscheinlich nicht einmal bewusst sind). An vorderster Stelle stehen dabei Memory Leaks und undefiniertes Verhalten. Wende mal
std::remove_if()
auf deine Sequenz an – viel Spass beim Rausfinden, was du noch freigeben musst. - Moderne Compiler und Standardbibliotheken sind optimiert. Dinge wie RVO oder Einsatz von
swap()
führt dazu, dass einige Kopien schon mal wegfallen. - Move-Semantik scheint dir gänzlich fremd zu sein. In C++0x hat man RValue-Referenzen, die können sehr gut Kopien vermeiden.
- Für jedes einzelne Element Speicher anzufordern und wieder freizugeben erfordert einen ziemlichen Geschwindigkeits- und Speicheroverhead. Gerade da
-
Akiko schrieb:
PS: Ich sehe es im Prinzip auch so wie du. Referenzen sind die C++ Antwort auf die C Pointer. Der erste C++ Entwurf sah sogar eine Abschaffung von C Pointern vor... vielleicht hätte man das tatsächlich tun sollen. Das fatale sind nicht die Pointer, sondern die Pointerarithmetik.
Ich denke nicht, dass Th69 das wirklich so sieht. Denn Zeiger abzuschaffen wäre eine ziemlich dumme Idee. Und nein, Referenzen sind nicht die Antwort auf Zeiger. Sie können sie zwar an ein paar Orten ersetzen, aber oft muss man immer noch auf Zeiger zurückgreifen. Das ist auch kein Problem, Zeiger sind äusserst nützlich. Nur sollte man besitzende Zeiger so gut es geht vermeiden.
Denn das Schlimme ist nicht wie du sagst Zeigerarithmetik, sondern vielmehr die inhärent fehleranfällige manuelle Speicherverwaltung. Gerade deshalb gibt es ja Container und Smart-Pointer. Und gerade deshalb sollte man auch Idiome wie RAII so oft wie möglich einsetzen, statt selbst mit
new
unddelete
zu hantieren.Akiko schrieb:
Spätestens bei Vererbung wird klar, warum man in Containern keine SmartPointer oder Objekte halten sollte...
Das hat nichts mit dem Beispiel hier zu tun. Und natürlich nimmt man nicht
vector<T*>
odervector<shared_ptr<T>>
, wenn manptr_vector<T>
benötigt.
-
Nexus schrieb:
Akiko schrieb:
Die höchste Effiziens erreicht man durch halten von Pointern und damit sind auch keine SmartPointer gemeint.
Also statt einem
std::vector<std::string> str_array;
lieber ein
std::vector<std::string *> str_array;
Wurde ja wieder mal Zeit, dass jemand so einen Unsinn schreibt.
- Für jedes einzelne Element Speicher anzufordern und wieder freizugeben erfordert einen ziemlichen Geschwindigkeits- und Speicheroverhead. Gerade da
std::string
wahrscheinlich selbst eher klein ist und nochmals Zeiger auf dynamischen Speicher hält, lohnt sich das nicht unbedingt. - Mit manueller Speicherverwaltung erledigst du die Aufgabe, die dir die STL-Container abnehmen sollten, wieder von Hand. Dabei nimmst du sämtliche Nachteile in Kauf (von denen dir einige wahrscheinlich nicht einmal bewusst sind). An vorderster Stelle stehen dabei Memory Leaks und undefiniertes Verhalten. Wende mal
std::remove_if()
auf deine Sequenz an – viel Spass beim Rausfinden, was du noch freigeben musst. - Moderne Compiler und Standardbibliotheken sind optimiert. Dinge wie RVO oder Einsatz von
swap()
führt dazu, dass einige Kopien schon mal wegfallen. - Move-Semantik scheint dir gänzlich fremd zu sein. In C++0x hat man RValue-Referenzen, die können sehr gut Kopien vermeiden.
- Jeder Aufruf von einem insert(), push_back(), push_front()... ist ein Aufruf vom Copy-Konstruktor. Was macht deiner Meinung nach ein Copy-Konstruktor? Und nochmal, das Grundprinzip der STL ist Kopieren. Oder kurz: Du legst das Objekt an und dann legst du es durch das einfügen in den Container nochmal an... Was ist da wohl effizienter, den Constructor von einem long int aufzurufen oder den von einem ganzen Objekt?
- Ja das stimmt, solange man nicht mit Vererbung arbeitet. Mal davon abgesehen, dass dir ein swap() beim einfügen in einen Container wenig nützt. Ja, ein remove_if wird nicht mehr ohne weiteres verwendbar sein, da aber ein remove_if auf ein std::vector ein lineares Zeitverhältnis hat, nimmt es sich nichts, das ganze auch mit einem for zu tun. Interessant wird es nur bei assoziativen Containern wie std::set oder std::map, da sie intern ausbalancierde binäre Bäume sind. Ja natürlich führt es zu mehr Problemen, die Frage ist nur, willst du effizient oder einfach Implementieren?
- Das Problem ist Ownership. Der Grund warum der Pointer in einem Container immer effektiver sein wird als Objekte ist die Tatsache, dass bei einem Pointer einfach nur die Ownership transferiert wird und nicht das ganze Objekt. Und das geht eben nur bei Pointern. Das ist der Segen und Fluch des Pointers.
Wenn du mal größere Projekte in BCPL geschrieben hast, wirst du sehen, was ich meine.
- Solange der C++0x Standard nicht fertig ist, braucht man da nicht drüber diskutieren. Habe vor zwei Stunden erst wieder gemerkt, dass bei meinem C++0x std::thread die cancel() Methode fehlt, obwohl std::thread schon eine halbe Ewigkeit nicht mehr im TR1 Namesraum ist.
Ich ziehe auch eindeutig Referenzen vor, gar keine Frage. Aber C++ ist nun mal "deffective by design", so ungern das einige C++ Fans auch hören mögen. (Die Sprachen Java, Objective C und vor allem Ada sind nicht grundlos entstanden.) Falls dich interessiert, warum ich so etwas schreibe, dann magst du vielleicht ja ml das hier lesen http://yosefk.com/c++fqa/. Es mag witzig geschrieben sein, aber es beinhaltet so viele Wahrheiten, dass man vor lauter Lachen weinen könnte.
Ich habe schon mehrere Projekte an genau diesen Gründen scheitern sehen.
PS: Ich muss an der Stelle zugeben, dass ich fast ausschließlich mit den Macken vom GCC und clang vertraut bin und anderen Compilern durchaus intelligenteres Verhalten zutraue.
- Für jedes einzelne Element Speicher anzufordern und wieder freizugeben erfordert einen ziemlichen Geschwindigkeits- und Speicheroverhead. Gerade da
-
Akiko schrieb:
Jeder Aufruf von einem insert(), push_back(), push_front()... ist ein Aufruf vom Copy-Konstruktor. Was macht deiner Meinung nach ein Copy-Konstruktor?
Mir ist die Funktionsweise eines Kopierkonstruktors durchaus bekannt, keine Sorge. Doch weder wird dieser immer aufgerufen, noch ist die dafür benötigte Zeit immer grösser als eine dynamische Allokation. Aber ich wiederhole mich.
Akiko schrieb:
Du legst das Objekt an und dann legst du es durch das einfügen in den Container nochmal an... Was ist da wohl effizienter, den Constructor von einem long int aufzurufen oder den von einem ganzen Objekt?
Die Prämisse ist schon falsch, siehe oben (übrigens haben skalare Datentypen keine Konstruktoren und ein Zeiger ist kein
long int
). Mir ist schon klar, was du mir sagen willst. Aber du tust so, als wäre das immer der Fall, was einfach nicht stimmt.Akiko schrieb:
Ja, ein remove_if wird nicht mehr ohne weiteres verwendbar sein, da aber ein remove_if auf ein std::vector ein lineares Zeitverhältnis hat, nimmt es sich nichts, das ganze auch mit einem for zu tun.
Klar, wieso auch Code wiederverwenden, wenn man ihn selbst schreiben kann?
Akiko schrieb:
Interessant wird es nur bei assoziativen Containern wie std::set oder std::map, da sie intern ausbalancierde binäre Bäume sind.
Was wird hier interessant?
Akiko schrieb:
Ja natürlich führt es zu mehr Problemen, die Frage ist nur, willst du effizient oder einfach Implementieren?
Beides, wobei Korrektheit sicher erste Priorität hat. Und genau deshalb mache ich nicht alles von Hand. Wenn du meinst, Zeiger unbedingt brauchen zu müssen, nimm
boost::ptr_vector
oder irgendwas anderes Fertiges. Aber du machst dir mit manueller Speicherverwaltung das Leben unnötig schwer. Einstd::vector
aus besitzenden Zeigern kann im kleinen Rahmen schon okay sein, aber sobald man etwas mehr Funktionalität braucht, ist das Nutzen-Kosten-Verhältnis nicht mehr gerechtfertigt.Akiko schrieb:
Solange der C++0x Standard nicht fertig ist, braucht man da nicht drüber diskutieren.
Natürlich nicht. Warum zukunftsorientiert denken, wenn dabei das Weltbild dabei Gefahr läuft, ins Wanken zu geraten?
Akiko schrieb:
Ich ziehe auch eindeutig Referenzen vor, gar keine Frage.
Nochmals: Es geht hier gar nicht um Referenzen vs. Zeiger.
Akiko schrieb:
PS: Ich muss an der Stelle zugeben, dass ich fast ausschließlich mit den Macken vom GCC und clang vertraut bin und anderen Compilern durchaus intelligenteres Verhalten zutraue.
Ich habe das Gefühl, du richtest deine Einstellung zu sehr nach einer konkreten Implementierung und spezifischen Low-Level-Angelegenheiten aus. Und weil du ein bestimmtes Verhalten oft beobachtet hast, weitest du das nun aus und machst es zu einem C++-Grundlagenproblem.
Und
vector<string*>
generell übervector<string>
zu stellen mit der Begründung, es sei immer schneller, ist einfach nur Unsinn. Abgesehen von all meinen Argumenten, warum das ein Trugschluss sein kann (es hängt halt sehr von der Verwendung ab), berücksichtigst du die Anforderungen des Benutzers gar nicht. Die Wahl eines Containers hängt von vielen Faktoren ab, und nicht immer ist Geschwindigkeit der entscheidende. Gerade bei jemandem, der sich erst in die STL einarbeitet, halte ich so einen Vorschlag für komplett verkehrt. Man schafft sich damit derart viele Probleme, die einfach nicht nötig wären, wenn man C++ richtig verstünde und sich nicht vorschnell auf dubiose Performancemythen einlassen würde. Sobald man einmal die Grundlagen versteht, kann man sich mit Mythen auseinandersetzen. Aber vorher ist das kontraproduktiv, weil man gar nicht das nötige Wissen besitzt, um die einzelnen Punkte beurteilen zu können.Dieses Halbwissen-Massenphänomen in C++ dürfte auch durchaus kleiner sein. Wie oft musste ich schon ohne jegliche Relativierung lesen, dass virtuelle Funktionen (oder noch schlimmer: OOP) langsam seien? Klar, die Sprache erfordert einen langen Weg für ein zusammenhängendes Verständnis, aber schlechte Bücher und Programmierer tragen auch ihren Teil zu jenen Irrtümern bei. Man sollte als Entwickler mindestens versuchen, solche Situationen kritisch zu hinterfragen und beide Seiten zu sehen.
Zusammengefasst: Warum meidest du RAII, obwohl du nicht ein einziges rationales Argument dagegen hast? Denn Performance lässt sich sehr gut damit vereinen. Hast du dich wirklich schon mit diesem elementaren Konzept in C++ befasst? Was ich von der Grundsatzhaltung "ich machs lieber selbst, weil es schneller ist" halte, kann ich gar nicht in Worten ausdrücken...
-
Akiko schrieb:
Falls dich interessiert, warum ich so etwas schreibe, dann magst du vielleicht ja ml das hier lesen http://yosefk.com/c++fqa/. Es mag witzig geschrieben sein, aber es beinhaltet so viele Wahrheiten, dass man vor lauter Lachen weinen könnte.
Ich habe schon mehrere Projekte an genau diesen Gründen scheitern sehen.
Man merkt recht schnell, dass C++-Bashing im Vordergrund steht. Natürlich treten auch nicht wenige Wissenslücken oder üble Fehlschlüsse auf. Sogar der Ex-Forentroll fricky hat schon yosefk zitiert. Nur ein paar kurze Dinge, die mir beim Überblicken gerade aufgefallen sind:
If you want to use C++, you must learn to love it the way it is, in particular, manage your memory manually.
Siehe meinen letzten Post. Hast du das von dort, Akiko?
Seriously, the syntax of smart pointers is the small problem. The big problem is their semantics. When you see a bare pointer, you know how it works. But a smart pointer can work in a lot of ways.
Das genaue Gegenteil ist der Fall. Bei einem Zeiger weiss man gar nichts. Man weiss nicht, wer ihn besitzt, wie der Besitz übertragen wird, auf welche Weise der Speicher angefordert wurde, etc. Smart-Pointer legen diese Dinge fest.
The inability to gracefully handle errors in C++ constructors is one good reason to avoid constructors that do more than nothing, and use initialization functions instead.
Das ist mir zu schade für eine Stellungnahme.
Naja, so jemanden kann ich nicht ernst nehmen. Es gibt durchaus berechtigte Kritiken an C++, aber die kommen etwas seriöser und objektiver rüber. Wenn tatsächlich ein Projekt an diesen Punkten scheitert, tun mir die Entwickler leid. Aber man muss ja nicht C++ nehmen, wenn man Reflection und Garbage Collection benötigt.
-
Nexus schrieb:
Seriously, the syntax of smart pointers is the small problem. The big problem is their semantics. When you see a bare pointer, you know how it works. But a smart pointer can work in a lot of ways.
Das genaue Gegenteil ist der Fall. Bei einem Zeiger weiss man gar nichts. Man weiss nicht, wer ihn besitzt, wie der Besitz übertragen wird, auf welche Weise der Speicher angefordert wurde, etc. Smart-Pointer legen diese Dinge fest.
Ist dein Englisch so schlecht oder wolltest du nicht auf das eingehen was er schreibt?
-
"When you see a bare pointer, you know how it works." → Nein, "Man weiss nicht, wer ihn besitzt, wie der Besitz übertragen wird, auf welche Weise der Speicher angefordert wurde, etc."
"But a smart pointer can work in a lot of ways." → Nein, "Smart-Pointer legen diese Dinge fest."shared_ptr
impliziert geteilten Besitz.scoped_ptr
impliziert einzigartigen, lokalen Besitz.auto_ptr
impliziert verschobenen Besitz.Er kritisiert doch, dass Smart-Pointer im Gegensatz zu normalen Zeigern eine unklare Semantik haben, weil es so viele Varianten (shared, scoped, auto...) davon gibt. Doch die einzelnen Varianten haben jeweils eine klare Besitzsemantik, im Gegensatz zu einem rohen Zeiger.
-
Nexus schrieb:
"When you see a bare pointer, you know how it works." → Nein, "Man weiss nicht, wer ihn besitzt, wie der Besitz übertragen wird, auf welche Weise der Speicher angefordert wurde, etc."
"But a smart pointer can work in a lot of ways." → Nein, "Smart-Pointer legen diese Dinge fest."shared_ptr
impliziert geteilten Besitz.scoped_ptr
impliziert einzigartigen, lokalen Besitz.auto_ptr
impliziert verschobenen Besitz.Er kritisiert doch, dass Smart-Pointer im Gegensatz zu normalen Zeigern eine unklare Semantik haben, weil es so viele Varianten (shared, scoped, auto...) davon gibt. Doch die einzelnen Varianten haben jeweils eine klare Besitzsemantik, im Gegensatz zu einem rohen Zeiger.
Besitz ist ownership, darüber redet er doch (absichtlich) garnicht. Dein Argument geht einfach an seiner Aussage vorbei, er sagt einfach, dass sie alle anders funktionieren, ob sein Argument sinnvoll ist, will ich garnicht sagen.
-
AKiko, du optimierst wie immer am Falschen Punkt.
Der Grund warum ein STL-Container kopiert ist, weil man Allgemein der Meinung ist, dass man wesentlich öfter auf ein Objekt zugreifen muss, als man Objekte einfügt oder entfernt. Insbesondere wenn man den Vektor wählt ist dies der Fall.
Ich behaupte nun: wenn ich einen STL-Container benutze wie er verwendet werden soll, werde ich Zeit gegenüber deiner Lösung sparen.
Warum?
1. Jedes mal wenn du auf ein Objekt zugreifst, erfordert dies eine weitere Dereferenzierung
2. Jedes mal wenn du einen Zeiger dereferenzierst, hast du die Chance auf einen Cache Miss
3. Cache Misses sind wahrscheinlicher, je fragmentierter der Speicher ist
4. new fragmentiert den Speicher.
5. Cache Misses bewegen sich ind er Größenordnung ~40ns
6. Beim vorwärts lesen von Speicheraddressen gibt es nso gut wie nie Cache misses.Fazit: du optimierst auf die im überfluss vorhandene Resource "CPU", belastest dafür aber dne Bottleneck "RAM". Was ist das bitteschön für eine bescheuerte Optimierung?
-
Wenn ich einen String erstelle/kopiere wird doch sowieso new für die Daten aufgerufen, oder? Dass da bei einer der beiden Versionen Cache Misses eine größere Rolle spielen bezweifle ich. Ihr könnt ja mal ein Testprogrammschreiben, aber eigentlich würde ich sagen: The first rule of optimization...
-
0815name schrieb:
The first rule of optimization...
...is to not optimize.
Ich wollte damit auch nur aussagen, dass er sich in teufels Küche optimiert, weil er zu kurzfristig denkt. Dass das Einfügen von Elementen in Arrays zeitkritisch ist, halte ich nämlich für höchst unwahrscheinlich.
Natürlich machen Cachemisses in dem Szenario nicht mehr so viel aus, da wir eh bereits wie wild durch den Speicher springen, aber man muss ja nicht absichtlich noch mehr springen.
-
Nexus schrieb:
Mir ist die Funktionsweise eines Kopierkonstruktors durchaus bekannt, keine Sorge. Doch weder wird dieser immer aufgerufen, noch ist die dafür benötigte Zeit immer grösser als eine dynamische Allokation. Aber ich wiederhole mich.
Hmm, wie kann ich das am besten erklären? Wenn du eine Instanz im Stack anlegst (ohne new), dann liegt die Ownership bei der umschließenden Strukur. Die Ownership kann nicht transferiert werden, da die angelegte Instanz Bestandteil des Stackframes damit der umgebenden Struktur ist. Beim Verlassen des Stackframes wird der beim Eintritt in die Struktur reservierte Speicher wieder freigeben und weg ist die Instanz. Bei einem anlegen auf dem Heap ist die Instanz im Prinzip global verfügbar (solange man den Pointer darauf nicht verliert) und man kann den Zugriff darauf als Pointer umherreichen. Das ist der Grund warum die Daten immer kopiert werden, wenn man im STL Container keine Zeiger auf die Daten hält. Wobei ich aber zugeben muss, dass ich nicht genau weiß, wie es aussieht, wenn eine Instanz auf dem Heap angelegt wird und dann normal per Referenz in einen STL Container bewegt wird. Denn an der Stelle könnte man tatsächlich einfach nur die Ownership übergeben. Fakt ist, wenn ein Objekt im Stack angelegt ist, muss es kopiert werden.
Nexus schrieb:
Akiko schrieb:
Du legst das Objekt an und dann legst du es durch das einfügen in den Container nochmal an... Was ist da wohl effizienter, den Constructor von einem long int aufzurufen oder den von einem ganzen Objekt?
Die Prämisse ist schon falsch, siehe oben (übrigens haben skalare Datentypen keine Konstruktoren und ein Zeiger ist kein
long int
). Mir ist schon klar, was du mir sagen willst. Aber du tust so, als wäre das immer der Fall, was einfach nicht stimmt.Ja richtig, da habe ich mich wohl etwas schwammig ausgedrückt. Der Abschnitt weiter oben sollte die Problematik erklären.
Nexus schrieb:
Akiko schrieb:
Ja, ein remove_if wird nicht mehr ohne weiteres verwendbar sein, da aber ein remove_if auf ein std::vector ein lineares Zeitverhältnis hat, nimmt es sich nichts, das ganze auch mit einem for zu tun.
Klar, wieso auch Code wiederverwenden, wenn man ihn selbst schreiben kann?
Die STL Methoden bleiben funktionstüchtig, wenn man statt der Pointer im STL Container einfach Pointer Container im STL Container verwendet. So dass man ganz im Sinne der RAII die Speicherverwaltung vom Constructor und Destructor des Pointer Containers machen lässt. Vielleicht hätte ich es gleich so schreiben sollen, dann wäre die Diskussion nicht so umfangreich geworden. Die Idee ist einfach, dass man so wenig wie möglich Daten hin und her kopieren muss.
Nexus schrieb:
Akiko schrieb:
Interessant wird es nur bei assoziativen Containern wie std::set oder std::map, da sie intern ausbalancierde binäre Bäume sind.
Was wird hier interessant?
Damit meine ich, dass hier for Schleifen deutlich langsamer sind als find, find_if, remove und so weiter.
Nexus schrieb:
Beides, wobei Korrektheit sicher erste Priorität hat. Und genau deshalb mache ich nicht alles von Hand.
Bei Anwendungsentwicklung sehe ich ganz genauso. Aber die Herangehensweise verschiebt sich, wenn man außerhalb eines Betriebsystems, innerhalb eines Kernels oder für embedded Systeme entwickelt.
Nexus schrieb:
Wenn du meinst, Zeiger unbedingt brauchen zu müssen, nimm
boost::ptr_vector
oder irgendwas anderes Fertiges. Aber du machst dir mit manueller Speicherverwaltung das Leben unnötig schwer.Ich habe mir nach vielen Tausenden Stunden Debuggen angewöhnt, so wenig wie möglich Kontrolle abzugeben. Dazu gehört auch so wenig wie möglich Third-Party Abhängigkeiten hineinzubringen. Wie schon gesagt, ich habe fast ausschließlich mit dem GCC zu tun und man hat nun mal nicht auf jedem System einen schnuckeligen neuen GCC 4.5.x.
Nexus schrieb:
Ein
std::vector
aus besitzenden Zeigern kann im kleinen Rahmen schon okay sein, aber sobald man etwas mehr Funktionalität braucht, ist das Nutzen-Kosten-Verhältnis nicht mehr gerechtfertigt.Auf einem normalen PC klar, aber es gibt Systeme, die stark limitierte Ressourcen haben und da muss man sich sehr gründlich überlegen, wie viel Speicher mal alloziert und wie viel man unnötigerweise hin und her kopiert. Es gibt Systeme, die erreichen nicht mal 5 MiB/s im RAM.
Nexus schrieb:
Akiko schrieb:
Solange der C++0x Standard nicht fertig ist, braucht man da nicht drüber diskutieren.
Natürlich nicht. Warum zukunftsorientiert denken, wenn dabei das Weltbild dabei Gefahr läuft, ins Wanken zu geraten?
Ich persönlich bin ein absoluter Fan von C++0x, nur dummerweise funktioniert so eine Herangehensweise nicht im produktiven Einsatz. Ich habe ein 5 Millionen Projekt den Bach runtergehen sehen, weil der Compiler und die damit verbundenen System Libs drei mal gewechselt wurden. Das mag im Windows Umfeld funktionieren, aber im Unix (und Linux) Umfeld funktioniert es nicht. Ich erlebe es jeden Tag aufs neue. Ein weiteres 2 Millionen Projekt hat bereits schon wieder ein Jahr Verzug. Da bekommt man echt Haarausfall...
Nexus schrieb:
Ich habe das Gefühl, du richtest deine Einstellung zu sehr nach einer konkreten Implementierung und spezifischen Low-Level-Angelegenheiten aus. Und weil du ein bestimmtes Verhalten oft beobachtet hast, weitest du das nun aus und machst es zu einem C++-Grundlagenproblem.
Natürlich tue ich das, im Unix Umfeld wirst du auf Dauer nichts anderes bekommen als ein GCC und ich bin nun mal ein Entwickler, der ein ganzes Spektrum für Unix-artige Systeme abdecken muss. Da ist vom Schreiben von Kerneltreibern (Linux), Consolen-Anwendungen über normale GUI-Anwendungen (zum Glück Qt4) bis hin zu massiv parallelen Systemen (mit 8 ADC Karten, die insgesamt über 100 Kanäle haben) und sehr ausgefallene FFTs durchführen, einfach alles dabei. Und das ganze in C, C++ , Ada, Perl und Bash... Und ja, es ist ein Problem von C++. Ada hat dieses Problem zum Beispiel nicht. Aber an der Stelle ist es auch nicht ganz fair C++ mit einer Military Style Security Language zu vergleichen, die sich vollständig selber verifizieren kann.
Nexus schrieb:
Gerade bei jemandem, der sich erst in die STL einarbeitet, halte ich so einen Vorschlag für komplett verkehrt.
Ja, da bin ich wohl etwas über das Ziel hinausgeschossen.
Nexus schrieb:
Man schafft sich damit derart viele Probleme, die einfach nicht nötig wären, wenn man C++ richtig verstünde und sich nicht vorschnell auf dubiose Performancemythen einlassen würde. Sobald man einmal die Grundlagen versteht, kann man sich mit Mythen auseinandersetzen.
So mythenhaft sind die nicht. Sind die Strukturen größer als die Hälfte des vorhandenen CPU-Caches, wird es recht eindeutig. Da haben wir Wochen mit verbracht um Code zu optimieren.
Nexus schrieb:
Dieses Halbwissen-Massenphänomen in C++ dürfte auch durchaus kleiner sein. Wie oft musste ich schon ohne jegliche Relativierung lesen, dass virtuelle Funktionen (oder noch schlimmer: OOP) langsam seien?
Das sehe ich ganz genauso. Ein Paradebeispiel ist hier wieder Ada, dass ganz ähnliche Performancelevel wie C Compilate erreichen. Die Ausnahme wäre dann wieder Objective-C, das stellenweise erschreckend langsam ist. Da können iOS Entwickler eine Lied von singen.
Nexus schrieb:
Klar, die Sprache erfordert einen langen Weg für ein zusammenhängendes Verständnis, aber schlechte Bücher und Programmierer tragen auch ihren Teil zu jenen Irrtümern bei. Man sollte als Entwickler mindestens versuchen, solche Situationen kritisch zu hinterfragen und beide Seiten zu sehen.
Davon kann ich auch ein Lied singen... Markt und Technik C/C++ Bücher... C und C++ in 24h... sind alle im Ofen gelandet. Auch ein paar ältere Sachen von Bjarne Stroustup haben mich leicht irritiert. Die Sachen von Scott Meyers waren bisher die akkuratesten, wobei ich Alexandrescu für besser halte.
Neulich durfte ich ein Projekt überprüfen, wo jemand mehr als 30 Threads verwendet hat und jeden einzelnen Thread (pthreads) detached hat und dann nicht so recht wusste, wie er feststellen soll, ob der Thread ordentlich zurückkehrt... und dann nahezu jede Klasse als Singleton... ich habe gedacht mich tritt ein Pferd.Nexus schrieb:
Zusammengefasst: Warum meidest du RAII, obwohl du nicht ein einziges rationales Argument dagegen hast? Denn Performance lässt sich sehr gut damit vereinen. Hast du dich wirklich schon mit diesem elementaren Konzept in C++ befasst? Was ich von der Grundsatzhaltung "ich machs lieber selbst, weil es schneller ist" halte, kann ich gar nicht in Worten ausdrücken...
Eieiei, da kam etwas falsch rüber. Ich predige meinen Leuten immer wieder, dass man in C++ Problemen aus dem Weg geht, indem man grundsätzlich überall RAII verwendet. Oder noch besser, so wie es die Standard C++ Lib auch macht, indem man in Kontruktoren und Destruktoren so wenig wie möglich Funktionalität bringt...
-
Nexus schrieb:
Man merkt recht schnell, dass C++-Bashing im Vordergrund steht. Natürlich treten auch nicht wenige Wissenslücken oder üble Fehlschlüsse auf. Sogar der Ex-Forentroll fricky hat schon yosefk zitiert. Nur ein paar kurze Dinge, die mir beim Überblicken gerade aufgefallen sind:
Das nennst du Bashing? Soll ich mal meine Meinung über Java kundtun? Danach bin ich dann wohl gesperrt.
Den Rest lass ich mal unkommentiert, das würde nur zu weiteren Diskussionen führen. Oh Moment: Ich bin ein C++, vor allem C++0x Fan und im Gegensatz zu <insert religion here>-Fans glaube ich nichts blind sondern schreibe mir einen ganzen Haufen Testprogramme um mir eine Problematik anzuschauen.
Nexus schrieb:
Akiko schrieb:
If you want to use C++, you must learn to love it the way it is, in particular, manage your memory manually.
Siehe meinen letzten Post. Hast du das von dort, Akiko?
Denk mal trüber nach. C++ hat vor C++0x kein eigenes Memory-Modell. Natürlich gilt das nicht mehr für C++0x, da hat man kapiert, dass man es braucht. Deswegen führt der C++0x Standard ja auch Shared, Auto, SmartPointer, Thread Local Storages, Threads ... ein, weil man eben gemerkt hat, dass es da eine Schwäche gibt.
Nexus schrieb:
Seriously, the syntax of smart pointers is the small problem. The big problem is their semantics. When you see a bare pointer, you know how it works. But a smart pointer can work in a lot of ways.
Das genaue Gegenteil ist der Fall. Bei einem Zeiger weiss man gar nichts. Man weiss nicht, wer ihn besitzt, wie der Besitz übertragen wird, auf welche Weise der Speicher angefordert wurde, etc. Smart-Pointer legen diese Dinge fest.
Ich habe nicht gesagt, dass der C++FQA überall Recht hat. Er regt zum Nachdenken an und das ist eine der Stellen die ich auch für Quatsch halte. Ich sehe das ganz ähnlich wie du.
Nexus schrieb:
The inability to gracefully handle errors in C++ constructors is one good reason to avoid constructors that do more than nothing, and use initialization functions instead.
Das ist mir zu schade für eine Stellungnahme.
Oh die mag ich. Die Standard C++ Lib wirft nur an einer einzigen Stelle eine Exception in einem Constructor und zwar bei new() und zur Krönung kann man es durch new(std::nothrow) abschalten, was ich persönlich sehr mag, denn wenn einem der Speicher ausgeht, kann es auch dazu kommen, dass nicht einmal mehr die std::bad_alloc Exception generiert werden kann. Halt Moment, das habe ich ja schon erlebt! Willkommen undefiniertes Verhalten... Aber das ist nicht ganz das gleiche. Ich würde die Aussage abändern zu: Fehlerbehandlung durch Exceptions in Kontruktoren sind schwierig.
Nexus schrieb:
Naja, so jemanden kann ich nicht ernst nehmen. Es gibt durchaus berechtigte Kritiken an C++, aber die kommen etwas seriöser und objektiver rüber. Wenn tatsächlich ein Projekt an diesen Punkten scheitert, tun mir die Entwickler leid. Aber man muss ja nicht C++ nehmen, wenn man Reflection und Garbage Collection benötigt.
Du bringst es auf den Punkt, vor allem der letzte Satz. Nur dummerweise denkt ein Großteil der Bestimmer in der Industrie, dass C++ die ultimative Lösung ist. Auch davon könnte ich schon wieder ein Lied singen.
-
@ Akiko
Okay, es scheint, als hätten grundsätzlich einen etwas anderen Bezug zu C++. Ich bin halt nicht so der Embedded- oder Kernelprogrammierer, darum kann ich die volle Standardbibliothek und oft auch Boost nutzen und muss nicht das Rad neu erfinden. Und persönlich habe ich die Erfahrung gemacht, dass Objeke im Container tendentiell eher schneller sind als Zeiger darauf, aber ich arbeite halt häufig mit kleinen Objekten und mit sich selten ändernden Containern. Klar, dass das nicht immer zutrifft. Für Ressourcen oder andere Heavyweight-Objekte benutze ich auch Zeiger (oder Vorallokierung), aber ich versuche manuelle Speicherverwaltung schnell mal wegzukapseln. Was die Mythen betrifft: Oft enthalten sie sogar ein Körnchen Wahrheit, aber meist wird eben nur die Hälfte davon gepredigt. Um beim Beispiel der virtuellen Funktionen zu bleiben: Diese sind zwar üblicherweise langsamer als normale Funktionen, doch das Verwenden eines anderen Dispatch-Mechanismus (if
,switch
, Funktionszeiger) benötigt auch seine Zeit.Aber ansonsten scheint es, als hätte ich dich an ein paar Orten (z.B. mit RAII) falsch verstanden, sorry dafür. Ich bin froh, dass wir das ähnlich sehen.
Und zum Text von yosefk: Ich glaube gern, dass er ein paar wahre Punkte anspricht. Aber viel davon ist Müll. Und dass er in jedem zweiten Punkt eine Anspielung darauf macht, wie schlecht C++ von Grund auf durchdacht ist und wie nahezu unmöglich es ist, sinnvoll damit zu programmieren, finde ich etwas schade. Das nimmt jegliche Ernsthaftigkeit und führt zu Ratereien, was der Autor wirklich für ein Problem hält und wo er nur trollen will.
Ich sehe zum Beispiel das Problem mit Exceptions im Konstruktor nicht ganz. Was ist da das Argument? Die Standardbibliothek wirft wahrscheinlich sehr selten Exceptions, weil sie ein breites Anwendungsspektrum hat und die Verwendung von Exceptions nicht immer gerechtfertigt oder sogar möglich ist. Aber es gibt z.B. bei den Streams sogar eine Methode, um Exceptions einzuschalten. Warum Exceptions im Normalfall sehr sinnvoll sind, erklärt dieser Artikel sehr gut.