RAII/RRID oder Zeiger...
-
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.
-
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::vectorist 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 nichtreserve()nutzt.boost::ptr_vectorwü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::listin 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_vectorwü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::strings?
-
Roger Wilco schrieb:
Wann sind Objekte groß? Könntest Du ein paar Beispiele oder Kriterien nennen, damit man als Anfänger eine gewisse Orientierung hat?
Es ist schwierig, eine klare Grenze zu ziehen. Nicht zuletzt ist die Objektgrösse ja nur eines von vielen Kriterien, die bei der Wahl des Containers berücksichtigt werden müssen. Wenn zum Beispiel die Anzahl Elemente im Voraus bekannt ist, kann man
reserve()benutzen und das Argument wegen teuren Kopierens fällt weg. Oder wenn man nur so selten neue Elemente einfügt, dass andere Operationen bei Weitem überwiegen.Grundsätzlich würde ich Instanzen von Klassen dazuzählen, die viele Daten speichern. Am populärsten für "heavy-weight"-Klassen sind wohl Ressourcen und Streams. Oft sind diese Klassen gar nicht kopierbar.
std::stringwürde ich persönlich irgendwo in der Mitte einstufen. Oft sind Strings nicht sehr gross und relativ schnell zu kopieren. Mit einemstd::vector<std::string>hätte ich kein Problem. Wenn allerdings nicht die Speicherung, sondern ein Algorithmus im Vordergrund steht (beispielsweise eine Permutation), kann es sinnvoll sein, statt der Objekte eine Indirektion durch Verweisen (IDs, Zeiger, ...) zu verwenden. Und sehr wichtig: Interne Kopien wo möglich durch Swap ersetzen. Zum Beispiel kann man auf diese Weise Elemente aus einemstd::vectorin O(1) löschen, sofern die Reihenfolge keine Rolle spielt.Das ist zumindest meine grobe Ansicht. Für einen konkreten Fall sollte man, wenn Geschwindigkeit ein kritisches Thema ist, viele Faktoren berücksichtigen und Zeiten messen. Dank des relativ einheitlichen Interfaces von STL- und Pointer-Containern ist ein Austausch nicht allzu aufwändig.
-
Der Hintergrund meiner Frage ist, dass ich hier die SmartPointer kennen gelernt habe und dann habe ich angefangen alle Zeiger durch shared_ptr zu ersetzen, um mich nicht um die Zerstörung und der Gültigkeit mehr kümmern zu müssen sowie Kopien zu vermeiden.
Nun wurde in den letzten Tagen wieder darauf hingewiesen, dass shared_ptr zu häufig verwendet und missbraucht werden. Und ich habe von den ptr-Container erfahren - wobei ich hier leider kein boost verwenden darf.
Jedenfalls fehlt mir noch das richtige Gespür dafür, wann man mit Stack-Objekten arbeitet, wann man Zeiger und wann man SmartPointer verwendet.
-
Roger Wilco schrieb:
Jedenfalls fehlt mir noch das richtige Gespür dafür, wann man mit Stack-Objekten arbeitet, wann man Zeiger und wann man SmartPointer verwendet.
Das ist auch etwas, wofür du einiges an Erfahrung sammeln musst. Und je nach Projekt können die Grenzen auch anders liegen. Grundsätzlich sollte man aber auch immer betrachten, wie wahrscheinlich eine Kopie überhaupt ist (und ggf. ob man überhaupt den richtigen Container gewählt hat).
Allgemein kann ich dir nur bei Zeigern eines sagen: Ich verwende native Zeiger nur, wenn diese kein Besitz halten, und Sichergestellt ist, das diese für die Zeit der Verwendung gültig bleiben (Ansonsten ist shared_ptr/weak_ptr durchaus zu überlegen). Ansonsten verwende ich immer Smartpointer (wenn auch möglichst erst "billige" [boost::scoped_ptr, std::auto_ptr...] und nur bei Bedarf Schwergewichte wie shared_ptr, die aber durchaus auch ihre Berechtigung haben).
Und was Stackvariablen angeht: Stell dir erst einmal die Frage wie groß sie sind, und wie häufig die Kopien wirklich vorkommen. Wenn ich mir z.B. einen reinen Adressdatensatz (Sowas wie Nachname, Vorname, PLZ, Ort...) anschaue, greife ich in der Regel immer zu Stackvariablen, obwohl viele std::string enthalten sind (Nur sind sie selten sehr groß). Anderseits stelle ich aber auch sicher das ich mit Ausnahme der Stellen wo ich keine Kopien vermeiden kann (z.B. std::vector) oder welche benötige, ich Objekte als Referenz übergebe (in der Regel als const &).
Und zu guter Letzt: Selbst wenn man boost nicht verwenden darf, kann man sich einfache Smartpointer häufig selbst bauen. So habe ich den scoped_ptr und eine etwas vereinfachte Form des shared_ptr in ein Projekt nachgebaut, das mit Prä-C++98 Compilern gearbeitet hatte [Und damit musste ich noch bis vor etwa 2-3 Jahren mit arbeiten].
-
asc schrieb:
Wenn ich mir z.B. einen reinen Adressdatensatz (Sowas wie Nachname, Vorname, PLZ, Ort...) anschaue, greife ich in der Regel immer zu Stackvariablen, obwohl viele std::string enthalten sind (Nur sind sie selten sehr groß). Anderseits stelle ich aber auch sicher das ich mit Ausnahme der Stellen wo ich keine Kopien vermeiden kann (z.B. std::vector) oder welche benötige, ich Objekte als Referenz übergebe (in der Regel als const &).
Danke für das Beispiel.
asc schrieb:
Allgemein kann ich dir nur bei Zeigern eines sagen: Ich verwende native Zeiger nur, wenn diese kein Besitz halten, und Sichergestellt ist, das diese für die Zeit der Verwendung gültig bleiben (Ansonsten ist shared_ptr/weak_ptr durchaus zu überlegen).
Könntest Du mir dafür ein Beispiel nennen?
-
Roger Wilco schrieb:
asc schrieb:
Allgemein kann ich dir nur bei Zeigern eines sagen: Ich verwende native Zeiger nur, wenn diese kein Besitz halten, und Sichergestellt ist, das diese für die Zeit der Verwendung gültig bleiben (Ansonsten ist shared_ptr/weak_ptr durchaus zu überlegen).
Könntest Du mir dafür ein Beispiel nennen?
Es ist jetzt ein wenig konstruiert, aber sollte als Beispiel etwas taugen.
Nehmen wir mal an, das wir eine polymorphe Basisklasse für Dokumenteninhalte haben (z.B. Grafikobjekte, Textblöcke...). Eine Dokumentenklasse "besitzt" diese Inhalte (z.B. mittels eines boost::ptr_vector). Der Besitz ist hier eindeutig: wenn das Dokument gelöscht wird, werden seine Inhalte gelöscht.
Jetzt gibt es vielleicht Inhalte, die auf andere verweisen. Da der Besitz aber geregelt ist, reicht hier ein einfacher Zeiger.
Falls aber der Besitz und die Freigabereihenfolge nicht sichergestellt ist (nehmen wir mal an, 3 Masken verweisen auf den gleichen Inhalt, und können unabhängig voneinander gelöscht werden), würde ich zu einem shared_ptr zurückgreifen (Die letzte Maske löscht den Inhalt).
-
Hm, hab noch so ein paar Fälle gefunden, wo man Zeiger nutzen muss:
* wenn man boost::signals für Objekte nutzt, die in Container sollen, da signal keinen copy-ctor hat
* sobald man Klassen hat, die in "kennt"-Beziehung zu anderen Klassen stehen, deren Objekte ihrerseits auf dem Heap angelegt werden müssen: das ist jetzt kein Muss, aber will ich Abhängigkeiten von Stack-Objekten zu Heap-Objekten?
-
Eisflamme schrieb:
Hm, hab noch so ein paar Fälle gefunden, wo man Zeiger nutzen muss:
* wenn man boost::signals für Objekte nutzt, die in Container sollen, da signal keinen copy-ctor hatNicht-Kopierbarkeit wurde schon genannt.
Eisflamme schrieb:
* sobald man Klassen hat, die in "kennt"-Beziehung zu anderen Klassen stehen, deren Objekte ihrerseits auf dem Heap angelegt werden müssen: das ist jetzt kein Muss, aber will ich Abhängigkeiten von Stack-Objekten zu Heap-Objekten?
Das verstehe ich nicht. Was spielt es für eine Rolle, ob die Klasse auf dem Heap oder Stack angelegt wurde? Wenn es sich bei den Zeigern lediglich um Verweise handelt, muss sich die Klasse gar nicht um Speicherverwaltung kümmern.
-
asc schrieb:
Nehmen wir mal an, das wir eine polymorphe Basisklasse für Dokumenteninhalte haben (z.B. Grafikobjekte, Textblöcke...). Eine Dokumentenklasse "besitzt" diese Inhalte (z.B. mittels eines boost::ptr_vector). Der Besitz ist hier eindeutig: wenn das Dokument gelöscht wird, werden seine Inhalte gelöscht.
Jetzt gibt es vielleicht Inhalte, die auf andere verweisen. Da der Besitz aber geregelt ist, reicht hier ein einfacher Zeiger.
Ich danke Dir für das Beispiel! Würden die Dokumenteninhalte allerdings nicht besonders große Objekte sein, dann würde man aber bevorzugt mit Stackvariablen und Referenzen arbeiten, richtig?