RAII/RRID oder Zeiger...



  • Klingt gut, werde ich Mal einstellen, falls es nicht schon ist. Aber wenn ich die Header aendere, ist das Neukompilieren dennoch erforderlich, auch wenn das vernachlaessigbar sein sollte.

    Welchen Vorteil hat 2) denn oder hat sich da irgend ein Troll Mal boeserweise so was ausgedacht? 😉



  • Eisflamme schrieb:

    Klingt gut, werde ich Mal einstellen, falls es nicht schon ist. Aber wenn ich die Header aendere, ist das Neukompilieren dennoch erforderlich, auch wenn das vernachlaessigbar sein sollte.

    Welchen Vorteil hat 2) denn oder hat sich da irgend ein Troll Mal boeserweise so was ausgedacht? 😉

    Keine Ahnung wie Du das genau meinst, aber das was Du beschreibst sollte das pImpl Idiom sein, welches durchaus eine legitime Methode ist die Implementierung zu verstecken um die API sauber zu halten. Lohnt sich aber eigentlich nur wenn man eine Library entwickelt.
    http://aszt.inf.elte.hu/~gsd/halado_cpp/ch09s03.html
    rya.



  • Hm, nein, Class2 ist in meinem Fall jetzt nicht die Implementierung von Class1. Das ist einfach irgendeine Klasse. Kann auch mehrere davon geben.


  • Mod

    Zu 1: Du willst aus Gründen der Performance eine Pointerindirektion einbauen? Das ist eine der verrücktesten Überlegungen die ich je gehört habe.



  • Verstehe nicht, wieso das verrückt sein soll.

    Sagen wir, ich habe eine Klasse, welche Daten auf ein Bild bereithält, die auch manipulierbar sein sollen. Eine Kopie eines Objektes bedeutet, das Bild zu kopieren.

    Wenn jetzt zwei Mal statt einmal ein copy-ctor aufgerufen wird, ist das fatal. Das gleiche könnte man für ganze Videos anführen.

    Oder man setzt auf kleine Objekte, wo man Mal 1000 Teile einfügen muss. Für ein Objekt mit Attributen, in denen mehr Tiefe steckt, hast Du dann wieder einen doppelten ctor-Aufruf.

    Generell finde ich, dass doppelte Einfügezeit sehr wohl ein ziemlich gewaltiger Performancenachteil ist, der dann auch stark gegen automatische Variablen spricht.


  • Mod

    Dann hast du aber eine ziemlich schlecht gebaute Bildklasse...



  • Wieso?


  • Mod

    Ups, hatte falsch gelesen. Du hattest mich mit den automatischen Variablen verwirrt, da ich annahm, dass dein Bild all seinen Speicher als automatische Membervariable halten soll. Wobei ich deine Bemerkung bezüglich der automatischen Variablen immer noch nicht verstehe.

    In diesen Fall kann dies tatsächlich Vorteile bringen, wenn das einmalige Kopieren tatsächlich länger dauern würde als die gesammelten Verluste bei jedem einzelnen Zugriff im weiteren Programmverlauf.



  • Die Variante 2 klingt für mich wie ein selbst gebastelter Smartpointer.
    Warum nicht statt dem hier

    vector<obj> vector1;
    obj blub(...);
    blub.stelleNochIrgendwasEin(...); // z.B. auch Klassen nutzen, die obj nicht kennt/kennen soll
    vector1.push_back(blub);
    

    das hier

    vector<obj> vector1;
    vector1.push_back(obj(...));
    vector1.back().stelleNochIrgendwasEin(...); // z.B. auch Klassen nutzen, die obj nicht kennt/kennen soll
    


  • Ich stelle mich ins Flutlicht und lasse mich dafür auslachen, dass ich nicht daran gedacht habe, dass Zugriffe auf Variablen über Zeiger langsamer sind als direkt auf sie zuzugreifen. 😉 Spielt das denn eine große Rolle? Wäre das (ich will nicht abschweifen und möchte, wenn überhaupt, nur eine kurze Antwort darauf) nicht ein Grund Java zu sagen, Java sei doch langsam? Sonst könnte ich mir vorstellen, man holt das bei einem etwas längerem Laden schnell rein.

    Selbst wenn die Summe meiner Zugriffszeiten weniger Zeit als die Bildkopie in Anspruch nehmen würde, müsste das dann nicht Mal ein Argument sein, wenn das Laden zeitunkritisch geschieht.

    Bei der automatischen Variable habe ich mich falsch ausgedrückt. Ich dachte bis eben, man nennt Stackvariablen automatisch, aber das macht so ja keinen Sinn.


  • Mod

    Eisflamme schrieb:

    Spielt das denn eine große Rolle?

    das kommt ganz auf das Programm an. Ein Programm, welches sehr viel auf die Daten zugreift, dabei aber nur wenig rechnet, würde massiv langsamer, andere möglicherweise kaum.

    Wäre das (ich will nicht abschweifen und möchte, wenn überhaupt, nur eine kurze Antwort darauf) nicht ein Grund Java zu sagen, Java sei doch langsam?

    Hehe, da sage ich lieber nichts zu, sonst endet das in einem Java-Flamethread.

    Bei der automatischen Variable habe ich mich falsch ausgedrückt. Ich dachte bis eben, man nennt Stackvariablen automatisch, aber das macht so ja keinen Sinn.

    Doch tut man. Ich war es der sich schlecht ausgedrückt hat.



  • Eisflamme schrieb:

    Hier wird ja immer dringend dazu geraten, dass man Zeiger weglaesst, sofern sie nicht noetig sind.

    Wobei man auch da immer von Fall zu Fall entscheiden muss (z.B. polymorphe Objekte), und Zeiger nicht gleich Zeiger ist (nativer Zeiger vs. Smartpointer).

    Eisflamme schrieb:

    Fuer mich waere es jetzt ueberhaupt kein Standardfall, einen vector<obj> statt vector<obj*> zu erstellen, allein wegen den Performancegruenden.

    Häufig macht dies in vielen Anwendungen nicht viel unterschied. Es kommt immer darauf an, wie groß die Objekte sind, und wie häufig tatsächlich Kopien nötig sind. In Hinblick auf C++0x (vermutlich C++11) solltest du deine Meinung hier aber nochmals ändern, durch Movesemantik etc. werden Objektcontainer vermutlich in der Performance deutlich unproblematischer als ohnehin schon.

    In bestimmten Bereichen benötigst du aber allgemein noch Zeiger oder vergleichbares, je nach Fall (besitztend oder nicht) würde ich aber zu den boost-Pointercontainern raten.

    Eisflamme schrieb:

    Die Laufzeit des Einfuegens ist dann doch sicher schlechter. Oder argumentiert ihr jetzt, dass solche Einfuegungen selten in Masse geschehen und daher nicht zeitkritisch sind? Falls ja, empfiehlt sich ein ptr_vector dann, falls es dann doch Mal zeitkritisch wird?

    1. Bezogen auf die Programmlaufzeit sind Kopien meistens nur dann kritisch, wenn diese wirklich häufig erfolgen (oder sehr groß sind).
    2. Pointercontainer würde ich immer dann wählen, wenn es wirklich performancekritisch ist, oder Zeiger nötig sind (Polymorphie) - UND der Container den Besitz hält (sonst tatsächlich std::vector<T*>).

    Eisflamme schrieb:

    Objekte erst zur Implementierungszeit bekannt machen?
    Ob gut oder nicht, ich habe bis vor kurzem gerne das hier genutzt:

    Was fehlerhaft ist (Da Kopierkonstruktor und Zuweisungsoperator in der Regel nötig sind, sobald auch nur ein Zeiger verwendet wird). Ganz davon abgesehen das du im Destruktor den Zeiger nicht auf 0 setzen musst, da die Variable eh nicht mehr verwendet wird, wenn das Objekt zerstört ist.

    Eisflamme schrieb:

    Geht natuerlich nur mit Zeigerattributen auf Objekte.

    Oder Smartpointern (Bei manchen Smartpointern ist eine Vorwärtsdeklaration möglich).

    Eisflamme schrieb:

    Vorteil: Compilezeit wird verringert, Header kennen allesamt nur Schnittstellen der Klassen.

    Ich verwende das Konstrukt (in ähnlicher Form, und in der Regel mit Smartpointern) auch, sofern der Typ entweder Polymorph ist, oder die Abhängigkeiten zu groß sind.



  • Durch solche Konstrukte handelst du dir oft noch seltsame Effekte ein, insbesondere wenn man die Big-3 Regel nicht beachtet ;).
    Ich löse solche Sachen dadurch, dass ich sie von boost::noncopyable ableite, um unbeabsichtigtes Kopieren zu verhindern (oder besser: unbedachtes Kopieren) und biete eine explizite clone() Methode an, wenn das Objekte irgendwie kopierbar sein soll. Damit stelle ich sicher, dass der Benutzer der Klasse ganz bewusst eine teure Kopie des Objektes erstellen will und weiß, was er gerade tut.
    Leider fällt dadurch die Möglichkeit weg, Objekte in Standardcontainern zu speichern, und weil mein Compiler die boost::ptr_container nicht kompiliert bleibts beim boost::shared_ptr.



  • Okay, dann bleibe ich mit den automatischen Variablen so weit klar. 🙂

    Die restlichen Punkte ergeben dann auch für mich Sinn, danke! Dicke 3er-Regel hab ich vergessen, ich gelobe Besserung. Das Zeiger nullen hab ich mir Mal so angewöhnt, macht aber in einem dtor bei genauerer Betrachtung wirklich überhaupt keinen Sinn.

    asc:
    Hast Du ein Beispiel, ab wann Abhängigkeiten so groß sind, dass Du eine nicht-polymorphe Klasse mit Zeiger speicherst?

    DocShoe:
    Das ist hart, oder? Wieso musst Du denn so einen Compiler nutzen?



  • Eisflamme schrieb:

    ...
    DocShoe:
    Das ist hart, oder? Wieso musst Du denn so einen Compiler nutzen?

    Es gibt Schlimmeres... boost ist nett, aber man kommt auch ohne aus, auch wenn´s etwas mehr Arbeit bedeutet. Die meisten Sachen aus boost funktionieren und die aktuelle Version der Compilersuite bringt sogar ihre eigene boost Bibliothek mit (ich denke, die wurde von den Compilerbauern angepasst, sodass da keine Probleme auftreten).
    Ist halt historisch, man hat sich damals auf einen Compiler festgelegt und nun ist der Umstieg auf einen anderen Compiler nur mit sehr viel Aufwand zu machen.



  • Okay. 🙂

    Vielen Dank nochmal an alle, hab wieder was dazu gelernt!



  • Eisflamme schrieb:

    asc:
    Hast Du ein Beispiel, ab wann Abhängigkeiten so groß sind, dass Du eine nicht-polymorphe Klasse mit Zeiger speicherst?

    Ich hatte schon in einigen Projekten gearbeitet, in der die ein oder andere Gottklasse (bzw. Blob) waren, die wiederum im Header 20+ Header inkludiert haben etc.

    Ich selbst verwende in eigenen Projekten eher kleine Klassen die meist recht billig zu kopieren sind.



  • DocShoe schrieb:

    ...und die aktuelle Version der Compilersuite bringt sogar ihre eigene boost Bibliothek mit (ich denke, die wurde von den Compilerbauern angepasst, sodass da keine Probleme auftreten)...

    ...und wurde auch kastriert (wenn du das RAD Studio meinst). Sie haben viele Bibliotheken entfernt, auch wenn teile davon Laufen. Beispiel: Die Pointercontainer (wobei boost::ptr_vector wohl unproblematisch ist, und wir die Bibliothek dafür einzeln kopiert haben).



  • Eisflamme schrieb:

    Fuer mich waere es jetzt ueberhaupt kein Standardfall, einen vector<obj> statt vector<obj*> zu erstellen, allein wegen den Performancegruenden. Fast jedes Objekt benoetigt doch mehr Speicher als ein herkoemmlicher Zeiger.

    Bei der Verwendung von Zeigern beeinflusst der Speicher für das Objekt zwar nicht die Kopiergeschwindigkeit, aber er ist genauso vorhanden – zusätzlich zum Speicher für den Zeiger selbst und zum versteckten Speicher für die Verwaltung des dynamischen Speicherbereichs. Der letzte sollte nicht vernachlässigt werden, besonders bei vielen kleinen Elementen fällt er (zusätzlich zur Allokationsgeschwindigkeit) ins Gewicht. Darum ist z.B. std::list<int> mit Vorsicht zu geniessen.

    Eisflamme schrieb:

    Die Laufzeit des Einfuegens ist dann doch sicher schlechter. Oder argumentiert ihr jetzt, dass solche Einfuegungen selten in Masse geschehen und daher nicht zeitkritisch sind? Falls ja, empfiehlt sich ein ptr_vector dann, falls es dann doch Mal zeitkritisch wird?

    std::vector ist ziemlich effizient für viele kleine bis mittlere Objekte mit Wertsemantik, die man mehr oder weniger sammelt, ohne intern viel durchzumischen. Bei grossen Objekten können die Reallokationen teuer werden, wenn man nicht reserve() nutzt. boost::ptr_vector würde ich vor allem aus folgenden Gründen einsetzen:

    • Objekte unterstützen keine Wertsemantik, d.h. in diesem Falle keinen Kopierkonstruktor oder Zuweisungsoperator.
    • Du brauchst gewisse Exceptionsicherheit-Garantien, die dir die STL nicht bieten kann.
    • Die Objekte sind gross oder du führst so viele interne Verschiebungen/Einfügungen/Löschungen durch, dass sonst zu viel kopiert wird. Allerdings sollte man hier auch andere Containertypen wie std::list in Betracht ziehen.

    Scorcher24 schrieb:

    Vorkompilierte Header beschleunigen das Bauen auch ungemein.

    Vorkompilierte Header sind ein Workaround und keine wirkliche Lösung. Sie ersetzen einen gut strukturierten, modularen Aufbau nicht. Trotzdem sind sie natürlich als Ergänzung sehr nützlich.

    Eisflamme schrieb:

    Das Zeiger nullen hab ich mir Mal so angewöhnt, macht aber in einem dtor bei genauerer Betrachtung wirklich überhaupt keinen Sinn.

    Das ist eine schlechte Angewohnheit. Von der Regel "setze Zeiger unmittelbar auf Null" halte ich nicht besonders viel, gerade weil sie eine falsche Sicherheit suggeriert und zu unnötigen Anwendungen verleitet. Natürlich sollte man Zugriff auf uninitialisierte Zeiger möglichst vermeiden. Aber das Beste bleibt nach wie vor, Zeiger gültig zu halten. Zu viele Statements der Form

    if (pointer)
       DoSomething(*pointer);
    

    sind nämlich ein Hinweis darauf, dass man in seinem Programm keine klare Gültigkeitssemantik definieren kann. Mühsam wird sowas, wenn dadurch wichtige Funktionen nicht ausgeführt werden, man aber wegen der "Sicherheitsabfrage" nichts davon merkt und leise Logikfehler im Programm hat. Dann lieber Nullzeiger dereferenzieren, auf den meisten Debug-Systemen führt das zu einem unmittelbaren Programmunterbruch.

    Es gibt unbestritten Anwendungsfälle, in denen so ein Verhalten auch sinnvoll ist. Jedoch sollte man wenn möglich gültige Objekte erzwingen. Das Schlimmste, was ich diesbezüglich je gesehen habe, war übrigens

    #define SAFE_DELETE(p) if(p) { delete p; } p = NULL;
    

    welches neben offensichtlichen Wissenslücken die eben genannten Probleme mit den Schwierigkeiten von Makros kombiniert und dadurch ein enormes Spektrum an Fehlern erst ermöglicht. Das Beste daran finde ich aber, dass das Makro auch noch unter dem Namen "safe" läuft.



  • Nexus schrieb:

    boost::ptr_vector würde ich vor allem aus folgenden Gründen einsetzen:
    [...]

    • Die Objekte sind gross (...)

    [...]

    Wann sind Objekte groß? Könntest Du ein paar Beispiele oder Kriterien nennen, damit man als Anfänger eine gewisse Orientierung hat?

    Was ist z.B. mit Objekten mit mehreren std::string s?


Anmelden zum Antworten