RAII/RRID oder Zeiger...
-
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?