Wie speichert ihr besitzende Zeiger in Containern?
-
Ich habe immer noch die vergebliche Hoffnung, dass dies mal wahr wird:
http://www.boost.org/doc/libs/1_47_0/libs/ptr_container/doc/ptr_container.html#future-developmentsThere are indications that the
void*implementation has a slight performance overhead compared to aT*based implementation. Furthermore, aT*based implementation is so much easier to use type-safely with algorithms. Therefore I anticipate to move to aT*based implementation.Aber das steht nun schon verdammt LANGE da!
Ansonsten schon Wrapper verwendet oder sowas hier:
http://www.boost.org/doc/libs/1_47_0/libs/utility/checked_delete.html
+
std::for_eachGesamthaft muss ich sagen: Nichts befriedigendes.
C++0x kommt bei mir noch kaum zum Einsatz.Was stört dich eigentlich an einem
std::vector<std::unique_ptr<T>>?Grüssli
-
Dravere schrieb:
There are indications that the
void*implementation has a slight performance overhead compared to aT*based implementation.Wieso denn? ob void* oder T*, im laufenden Programm sollte es da doch keinen Unterschied geben.
-
Dravere schrieb:
Ich habe immer noch die vergebliche Hoffnung, dass dies mal wahr wird
Wahrscheinlich haben sie
void*gewählt, um den Template-Bloat in Grenzen zu halten. Für jeden Typen neue Instanziierungen zu erstellen, obwohl die Zeiger alle gleich behandelt werden, ist ja wirklich sinnlos. Und im Prinzip hat Cachus Recht, das ist ein Implementierungsdetail. Höchstens für den Debugger ist es relevant, aber selbst mitT*wäre das noch extrem benutzerunfreundlich. Aber hier habe ich ja eine Lösung.Dravere schrieb:
Ansonsten schon Wrapper verwendet
Um
std::vector<T*>und Co., oder wie sieht das bei dir aus?Dravere schrieb:
Was stört dich eigentlich an einem
std::vector<std::unique_ptr<T>>?Vor allem die zusätzliche Indirektion beim Element- und Iteratorzugriff sowie die Notwendigkeit, alle Objekte erst in Smart-Pointer zu packen.
-
Nexus schrieb:
Vor allem die zusätzliche Indirektion beim Element- und Iteratorzugriff sowie die Notwendigkeit, alle Objekte erst in Smart-Pointer zu packen.
Das hat aber den Vorteil, dass der Code direkt ausdrückt, was er tut, und somit verständlicher wird. Zumindest meiner Meinung nach.
Die automatische Dereferenzierung bei denboost.pointer*-Teilen finde ich eher suspekt.
-
Ich finde die direkte Dereferenzierung im Hinblick auf Abstraktion gut. In erster Linie will ich ja Objekte speichern – dass ich dafür Zeiger verwende, ist vor allem eine technische Angelegenheit (z.B. um Slicing zu verhindern). Im Weiteren sind Nullzeiger standardmässig verboten, ich habe also nicht wirklich viel von diesem Wissen. Gerade mit Move-Semantik verschwimmen die Grenzen noch mehr, da ein Objekt keinen festen Platz mehr hat.
Ohne diese Dereferenzierung müsste man generischen Code verdoppeln, einmal für STL-Container und einmal für Boost-Pointer-Container. Oder immer irgendwelche Umwandler benutzen. Man könnte nicht von einem
std::vector<T>aufboost::ptr_vector<T>wechseln, ohne sämtlichen Anwendercode anzupassen. Nun muss man das hauptsächlich noch an den Stellen, wo man neue Elemente einfügt. Ausserdem sind STL-Algorithmen mühsam auf Zeigern anzuwenden, vor allem wenn man schon fertige Funktoren hat, die mit Referenzen arbeiten.
-
Nexus schrieb:
Dravere schrieb:
Ich habe immer noch die vergebliche Hoffnung, dass dies mal wahr wird
Wahrscheinlich haben sie
void*gewählt, um den Template-Bloat in Grenzen zu halten. Für jeden Typen neue Instanziierungen zu erstellen, obwohl die Zeiger alle gleich behandelt werden, ist ja wirklich sinnlos.Wird sowieso passieren. Problem ist der zugrunde liegende Container. Die Boost Pointer Container sind eigentlich nur Wrapper. Sieh dir mal die Deklaration von
boost::ptr_vectoran:template < class T, class CloneAllocator = heap_clone_allocator, class Allocator = std::allocator<void*> > class ptr_vector : public ptr_sequence_adapter < T, std::vector<void*,Allocator>, CloneAllocator >Wieso wird da ein
std::vector<void*,Allocator>verwendet statt einesstd::vector<T*,Allocator>? Wenn du dir den Code dann weiter anschaust, findest du überallstatic_cast. Natürlich schön weggekapselt (also der Typ, welcher das programmiert hat, hat schon was drauf), aber wieso er sich fürvoid*entschieden hat, ist mir ein absolutes Rätsel. Macht die Sache eigentlich nur unnötig komplizierter.Nexus schrieb:
Und im Prinzip hat Cachus Recht, das ist ein Implementierungsdetail. Höchstens für den Debugger ist es relevant, aber selbst mit
T*wäre das noch extrem benutzerunfreundlich. Aber hier habe ich ja eine Lösung.Nicht ganz. Man kann an die zugrunde liegende Container rankommen. Sie sind somit ein Teil der Schnittstelle und somit kein Implementierungsdetail mehr. Und dort findest du dann alles
void*Zeiger vor.Frag mich jetzt nicht, wieso ich mal da ran wollte, ich weiss es nicht mehr. Ich weiss nur noch, dass es aus irgendeinem Grund nicht anders ging

Und benutzerunfreundlich finde ich es auch nicht. Du musst nur in
boost::ptr_container_detail::reversible_ptr_containeran die private Variable_cran. Das ist der Container, welcher gewrappt wurde. Wenn da keinevoid*Zeiger drin wären, wäre es kein Problem.Nexus schrieb:
Dravere schrieb:
Ansonsten schon Wrapper verwendet
Um
std::vector<T*>und Co., oder wie sieht das bei dir aus?Ja, grundsätzlich ähnlich, wie es Boost Pointer Container macht, nur halt einfacher und auf meine Bedürfnisse zugeschnitten.
Grüssli
-
Dravere schrieb:
[Template Bloat] Wird sowieso passieren. Problem ist der zugrunde liegende Container. Die Boost Pointer Container sind eigentlich nur Wrapper.
Naja, zumindest auf unterster Ebene werden
void*in STL-Containern gespeichert. Ich kann jetzt schlecht einschätzen, wie weit man die Type Erasure durchziehen kann, vielleicht wäre noch mehr möglich gewesen. Aber wenn man Bloat vermeiden kann, finde ichvoid*gut, zumal es normalerweise völlig vom Benutzer abgekapselt ist.Dravere schrieb:
Und benutzerunfreundlich finde ich es auch nicht. [...] Wenn da keine
void*Zeiger drin wären, wäre es kein Problem.Okay, hier mal die Debug-Anzeige von
boost::ptr_map:- map boost::ptr_map<int,Base,std::less<int>,boost::heap_clone_allocator,std::allocator<std::pair<int const ,void *> > >
- boost::ptr_map_adapter<Base,std::map<int,void *,std::less<int>,std::allocator<std::pair<int const ,void *> > >,boost::heap_clone_allocator,1>
- boost::ptr_container_detail::ptr_map_adapter_base<Base,std::map<int,void *,std::less<int>,std::allocator<std::pair<int const ,void *> > >,boost::heap_clone_allocator,1>
- boost::ptr_container_detail::associative_ptr_container<boost::ptr_container_detail::map_config<Base,std::map<int,void *,std::less<int>,std::allocator<std::pair<int const ,void *> > >,1>,boost::heap_clone_allocator>
- boost::ptr_container_detail::reversible_ptr_container<boost::ptr_container_detail::map_config<Base,std::map<int,void *,std::less<int>,std::allocator<std::pair<int const ,void *> > >,1>,boost::heap_clone_allocator>
- c_ std::map<int,void *,std::less<int>,std::allocator<std::pair<int const ,void *> > >
- [0] std::pair<int const ,void *>
Kein Problem? Mit der Benutzerfreundlichkeit ist es spätestens nach drei Indirektionen vorbei. Und es sind sieben!

Was interessieren mich
ptr_map_adapter,ptr_map_adapter_base,associative_ptr_container,reversible_ptr_containerundstd::mapund all deren Templateparameter? Ich will direkt auf die relevanten Member vonboost::ptr_mapzugreifen. Mit den Visualizers geht das sehr schön, das ganze Geboostel wird wegabstrahiert:- map boost::ptr_map<int,Base,std::less<int>,boost::heap_clone_allocator,std::allocator<std::pair<int const ,void *> > >
- [0] std::pair<int,Base *>
Dravere schrieb:
Man kann an die zugrunde liegende Container rankommen. Sie sind somit ein Teil der Schnittstelle und somit kein Implementierungsdetail mehr. Und dort findest du dann alles void* Zeiger vor.
Bis zur untersten Ebene? Darüber scheint ja alles mit
T*abzulaufen. Aber ich kann mir nicht vorstellen, wozu man das möchte. Vielleicht haben sie es angeboten, um maximale Flexibilität zu ermöglichen, aber aus Abstraktionssicht ist das sicher nicht klug. Zumal sie so die Implementierung nicht ändern können, ohne Benutzercode kaputt zu machen. Von mir aus können sie ruhig überallvoid*verwenden, solange das Interface sauber bleibt und ich vernünftig debuggen kann. Wenn dadurch der erzeugte Code kleiner wird, umso besser.
-
Nexus schrieb:
Okay, hier mal die Debug-Anzeige von
boost::ptr_mapMusstest natürlich gleich etwas vom schlimmsten nehmen. Ich habe halt bisher eher mit
boost::ptr_vectorgearbeitet. Da sind es deutlich weniger. Aber ist eigentlich auch egal. Ich arbeite in VS oft mit dem Immediate Fenster zusammen, um Werte zu analysieren. Da ist es völlig egal, wieviele Basisklassen das Ding hat. Wenn du z.B. einenboost::ptr_vector vechast, dann gibst du im Immediate Fenster einfachvec.c_ein. Schon hast du alle Werte, welche sich im vec.c_ Container befinden. Kannst auch auf ein einzelnes Element zugreifen:vec.c_._Myfirst[0]. Bringt aber natürlich nichts, weil es einvoid*Zeiger ist und somit erhälst du nur den Zeigerwert.Aber gut, jeder wie er mag

Nexus schrieb:
Dravere schrieb:
Man kann an die zugrunde liegende Container rankommen. Sie sind somit ein Teil der Schnittstelle und somit kein Implementierungsdetail mehr. Und dort findest du dann alles void* Zeiger vor.
Bis zur untersten Ebene? Darüber scheint ja alles mit
T*abzulaufen. Aber ich kann mir nicht vorstellen, wozu man das möchte. Vielleicht haben sie es angeboten, um maximale Flexibilität zu ermöglichen, aber aus Abstraktionssicht ist das sicher nicht klug. Zumal sie so die Implementierung nicht ändern können, ohne Benutzercode kaputt zu machen. Von mir aus können sie ruhig überallvoid*verwenden, solange das Interface sauber bleibt und ich vernünftig debuggen kann. Wenn dadurch der erzeugte Code kleiner wird, umso besser.Man kommt z.B. an den Container ran, welcher gewrappt wurde:
http://www.boost.org/doc/libs/1_47_0/libs/ptr_container/doc/reversible_ptr_container.html#modifiersVoidPtrContainer& base();
const VoidPtrContainer& base() const;
Returns: a reference to the wrapped containerGrüssli
-
Dravere schrieb:
Musstest natürlich gleich etwas vom schlimmsten nehmen.
Ja, schliesslich habe ich auch schon mit
boost::ptr_mapgearbeitet. Ist ohnehin etwas gewöhnungsbedürftig, ich mag z.B. die Referenzübergabe für den Key nicht (auch wenn sie für Exceptionsicherheit notwendig ist). Etwas schade, da Key-Kopien bei mir eigentlich nie Exceptions werfen.Dravere schrieb:
Aber gut, jeder wie er mag

Hast du nicht auch vor, in Zukunft diese Visualizers zu verwenden? Ich finde sie wirklich sehr nützlich. Ich fahre z.B. ab und zu mit der Maus über eine Variable, um ihren Wert anzuschauen. Da zahlt es sich aus, nicht 6 mal ein neues Untermenü aufklappen zu müssen, um nachher die
void*zu begutachten
Aber zurück zum eigentlichen Thema: Kennt jemand andere Implementierungen für Pointer-Container als die von Boost? Oder gibt es weitere Leute, die sich Wrapper schreiben?
-
Ich verwende bisher nur einmal einen boost::ptr_vector<abc> zwecks Polymorphie. Das würde aber genauso mit std::vector<unique_ptr<abc> > auch funktionieren. Von der Standardbibliothek nutze ich aber ungern C++0x Features. Deswegen ist es bei diesem einen Male ein boost::ptr_vector geworden.
Dass ich die ptr_vector-Iteratoren nicht doppelt dereferenzieren muss, um an das abc-Objekt zu kommen, finde ich gut. Aber super wichtig ist das jetzt auch nicht. Im Notfall gibt es so Dinge wie boost::indirect_iterator.
-
krümelkacker schrieb:
Von der Standardbibliothek nutze ich aber ungern C++0x Features.
Wegen unausgereifter Implementierungen bzw. möglicher Bugs? Ich selbst verwende hauptsächlich die Dinge, die es im TR1 schon gab (
function,bind,array,shared_ptr). Aber ich kenne viele Neuerungen auch gar nicht. Aber die Threading-Sachen muss ich mir unbedingt einmal anschauen.krümelkacker schrieb:
Im Notfall gibt es so Dinge wie boost::indirect_iterator.
Klar, aber der direkte Weg scheint mir dennoch intuitiver. Und wie erwähnt ist generischer Code wahrscheinlich etwas einfacher zu schreiben.
Danke auch für deinen Beitrag! Weitere Meinungen sind natürlich gerne gesehen
