Problem mit Forward Declaration bei selbstgeschriebenem shared_ptr
-
Hm... Im Internet findet sich keine konkrete Beschreibung zur Indirektion und ich kann weder aus den kryptischen C++0x-Headern herausfinden, wie das funktioniert (dort finde ich nicht mal einen Destruktor von shared_ptr), noch mir vorstellen, wie das funktionieren soll. Kannst du mir einen Anhaltspunkt geben?
-
Funktionszeiger, die im Konstruktor mit
T*initialisiert werden. Bei Kopien, Zuweisungen oder der Zerstörung des Smart-Pointers muss dann das referenzierte Objekt nicht vollständig bekannt sein, der Aufruf durch den Funktionszeiger abstrahiert den Typ.Die Alternative wäre Laufzeitpolymorphie, aber im Prinzip brauchst du kein Objekt dafür (und sparst dir somit auch dessen Speicherverwaltung).
-
Nexus schrieb:
Funktionszeiger, die im Konstruktor mit
T*initialisiert werden. Bei Kopien, Zuweisungen oder der Zerstörung des Smart-Pointers muss dann das referenzierte Objekt nicht vollständig bekannt sein, der Aufruf durch den Funktionszeiger abstrahiert den Typ.Die Alternative wäre Laufzeitpolymorphie, aber im Prinzip brauchst du kein Objekt dafür (und sparst dir somit auch dessen Speicherverwaltung).
Die "dumme" Interpretation deines ersten Satzes wäre ja diese hier:
template<typename T> class shared_ptr { void (*delete_func)(); T *ptr; public: shared_ptr(T *in) : ptr(in) , delete_func(in) //<-DAS { //... } //... };Da ich natürlich weiß, dass das so nicht geht, würde ich gerne wissen, wie du das gemeint hast. Ich bin auf die Idee gekommen, statt dem Funktionszeiger einen Zeiger auf eine abstrakte Basisklasse zu nehmen und dann new Deleter<T>(ptr) darin zu speichern (wobei Deleter dann von der Basisklasse erben würde). Dabei könnte aber ein bad_alloc geworfen werden (was hier natürlich unerwünscht ist).
Meinst du das so?
template<typename T> void delete_obj(void *obj) { if(obj != NULL) { //reference counter erniedrigen if(ref_count == 0) delete reinterpret_cast<T *>(obj); } } template<typename T> class shared_ptr { void (*delete_func)(void *); T *ptr; public: shared_ptr(T *in) : ptr(in) , delete_func(reinterpret_cast<void (*)(void *)>(delete_obj<T>)) { //... } ~shared_ptr() { delete_func(static_cast<void *>(ptr)); } //... };
-
http://blogs.msdn.com/b/vcblog/archive/2011/02/11/10128357.aspx

Eine vernünftige Implementierung lässt auch Konvertierungen zu ... also shared_ptr<T> --> shared_ptr<const T> und shared_ptr<Derived> --> shared_ptr<Base>. Dann gibt es noch die Sache mit weak_ptr. Und da man sowieso für den Referenzzähler etwas dynamisch allozieren muss, kann man auch einen Deleter in Form eines Funktors dranhängen. Und Thread-sicher sollten die Referenzzähler auch sein. Mit anderen Worten: Nimm gleich std::tr1::shared_ptr ...
-
Diese Video über den shared_ptr ist relativ interessant und vom Maintainer der VC++ STL: http://channel9.msdn.com/Shows/Going+Deep/C9-Lectures-Stephan-T-Lavavej-Advanced-STL-1-of-n

-
@wxSkip:
Der Deleter wird normalerweise im Shared-Count mit abgespeichert, und by-value an den Konstruktor von shared_ptr übergeben.
Der Default-Deleter wird durch einen Default-Parameter des Konstruktors erzeugt.Vereinfacht:
struct shared_count_base { size_t m_strong_refs; size_t m_weak_refs; virtual void DoDelete() = 0; }; template <class T, class D> struct shared_count : shared_count { D m_deleter; shared_count(D d) : m_deleter(d) {} virtual void DoDelete(T* t) { m_deleter(t); } }; template <class T> struct shared_ptr { T* m_t; shared_count_base* m_sc; template <class D> static shared_count_base* make_shared_count(T* t, D d) { std::auto_ptr<T> guard(t); shared_count_base* sp = new shared_count<T, D>(d); guard.release(); return sp; } template <class D> shared_ptr(T* t, D d = default_deleter<T>()) : m_t(t), m_sc(make_shared_count(t, d)) {} };Dass dabei ein
bad_allocfliegen kann kann man nicht verhindern, irgendwoher muss dershared_countja kommen.Damit es nicht zu Memory-Leaks kommt, wird das übergebene Objekt in so einem Fall einfach gelöscht.
BTW: der übergebene Deleter muss garantiert no-throw kopierbar sein. Wüsste auch nicht wie man das besser machen könnte. (Ein function-try Block fängt ja auch keine Exceptions die beim Kopieren der Parameter auftreten ... oder doch? - ne, kann IMO nicht sein.)
-
hustbaer schrieb:
Vereinfacht:
struct shared_count_base { size_t m_strong_refs; size_t m_weak_refs; virtual void DoDelete() = 0; }; template <class T, class D> struct shared_count : shared_count { D m_deleter; shared_count(D d) : m_deleter(d) {} virtual void DoDelete(T* t) { m_deleter(t); } };Außer dem Deleter müsstest Du hier auch noch den Zeiger speichern, damit DoDelete ohne Parameter aufgerufen werden kann. Und shared_count_base braucht noch einen virtuellen Destruktor.
-
@krümelkracker+hustbaer:
OK, ich habe jetzt auch bemerkt, dass der shared_ptr-Konstruktor doch etwas werfen kann. Es bleibt aber doch die Frage: Glaubt ihr, mein Vorschlag würde funktionieren (zumindest kompiliert er ohne Probleme)? Ich brauche auch nur shared_ptr mit den hier angegebenen Funktionen (und vielleicht Vergleichsoperatoren oder etwas ähnlich einfachem).
-
wxSkip schrieb:
Glaubt ihr, mein Vorschlag würde funktionieren [...]?
Nein. Wenn der Typ von
*ptrunvollständig ist, dann wird bei einemdelete ptr;möglicherweise nicht der richtige delete-Operator aufgerufen und auch keinesfalls irgendein Destruktor.wxSkip schrieb:
(zumindest kompiliert er ohne Probleme)
Du meinst, abgesehen von der Warnung? :p
Das fehlerfreie Kompilieren ist keine Garantie dafür, dass es auch funktioniert.
-
krümelkacker schrieb:
hustbaer schrieb:
Vereinfacht:
struct shared_count_base { size_t m_strong_refs; size_t m_weak_refs; virtual void DoDelete() = 0; }; template <class T, class D> struct shared_count : shared_count { D m_deleter; shared_count(D d) : m_deleter(d) {} virtual void DoDelete(T* t) { m_deleter(t); } };Außer dem Deleter müsstest Du hier auch noch den Zeiger speichern, damit DoDelete ohne Parameter aufgerufen werden kann. Und shared_count_base braucht noch einen virtuellen Destruktor.
Ja, stimmt beides

Den Destruktor hab' ich schlicht vergessen.
Und beim Deleter hatte ich mich davon irreleiten lassen dass in den shared_ptr Instanzen ja ein Zeiger abgespeichert ist. Bloss der muss ja mit dem ursprünglichen T* gar nix mehr zu tun haben.(Ich seh grad da sind auch noch ein haufen andere Flüchtigkeitsfehler drin :D)
-
krümelkacker schrieb:
...
ich meinte den Vorschlag mit dem Template-Funktionspointer. Nachlesen!
-
wxSkip schrieb:
krümelkacker schrieb:
...
ich meinte den Vorschlag mit dem Template-Funktionspointer. Nachlesen!
Mach mal den reinterpret_cast weg.
Und den Zeiger im shared_ptr einfach so an den Deleter übergeben ist auch nicht gut. Wenn T* nichtmehr das ursprüngliche T* ist kann das Probleme machen, weil z.B. der falsche Destruktor ausgeführt wird.BTW: wo speicherst du denn den Referenz-Zähler? Hast du kein "shared count" Objekt? Verwendest du etwa ne Linked-List statt nem Zähler?
-
hustbaer schrieb:
wxSkip schrieb:
krümelkacker schrieb:
...
ich meinte den Vorschlag mit dem Template-Funktionspointer. Nachlesen!
Mach mal den reinterpret_cast weg.
Und den Zeiger im shared_ptr einfach so an den Deleter übergeben ist auch nicht gut. Wenn T* nichtmehr das ursprüngliche T* ist kann das Probleme machen, weil z.B. der falsche Destruktor ausgeführt wird.BTW: wo speicherst du denn den Referenz-Zähler? Hast du kein "shared count" Objekt? Verwendest du etwa ne Linked-List statt nem Zähler?
Das mit dem reinterpret_cast habe ich jetzt nur gemacht, weil ich nicht genau wusste, ob das so zulässig ist, ich hätte es mir aber eigentlich denken können.
Wie soll der T * nicht mehr der ürsprüngliche T * sein? Es kann ja erst ein neues Objekt an der Speicheradresse alloziert werden, wenn das alte gelöscht wurde. Und das ist ja vorher noch nicht der Fall. Ich habe auch nicht vor, irgendwelche krummen Pointer-Hacks zu machen. Dass shared_ptr<Base>(new Derived) den Derived-Destruktor aufruft, auch wenn er nicht virtuell ist, ist hier zwar nicht gegeben, aber ich könnte es noch hinzufügen und außerdem mache ich meine Destruktoren sowieso immer virtuell, wenn ich ihn so benutze.Der shared_count ist bei mir durch die ref_count-Map realisiert (ganz oben im Code).
-
wxSkip schrieb:
krümelkacker schrieb:
...
ich meinte den Vorschlag mit dem Template-Funktionspointer. Nachlesen!
Was Du mit "mein Vorschlag" gemeint hast, war nicht eindeutig.
void* -> T* geht auch mit einem static_cast
Um bei dem Ansatz die Konvertierung shared_ptr<derived> --> shared_ptr<base> zulassen zu können, müsstest Du einen zusätzlichen void* im shared_ptr speichern. Dann hättest Du drei Zeiger:
- T*
- void*
- void()(void)
Warum müsstest Du das? Weil derived* -> base* -> void* -> derived* nicht garantiert verlustfrei ist (Stichwort pointer adjustments)Was hindert Dich den daran, folgendes zu benutzen:
struct sp_control_block { size_t m_strong_refs; size_t m_weak_refs; sp_control_block() : m_strong_refs(0), m_weak_refs(0) {} virtual void do_delete() = 0; virtual ~sp_control_block() {} }; template <class T, class D> struct sp_cb_plus_deleter : sp_control_block { T* m_ptr; D m_deleter; explicit sp_cb_plus_deleter(T* p, D d = D()) : m_ptr(p), m_deleter(d) {} virtual void do_delete() { m_deleter(m_ptr); m_ptr = 0; } }; template<class T> struct default_deleter { void operator()(T* ptr) const {delete ptr;} }; template<class T, class D> inline sp_control_block* alloc_control_block(T* obj, D del) { return sp_cb_plus_deleter<T,D>(obj,del); } template<class T> inline shared_count_base* alloc_control_block(T* obj) { return alloc_control_block(obj,default_deleter<T>()); }?
oder gar std::tr1::shared_ptr?
-
wxSkip schrieb:
Der shared_count ist bei mir durch die ref_count-Map realisiert
Damit kommst Du zwar um einen zusätzlichen Zeiger im shared_ptr herum, bedeutet aber auch Nachteile (globales Objekt muss entsprechend geschützt werden falls Du mehrere Threads laufen lässt, und Du kannst keine eigenen Deleter verwenden)
-
1. Ja, das war nicht eindeutig
. Ich habe bloß vermisst, dass jemand auf meinen Beitrag/meine Frage eingeht.
2. "pointer adjustments": Wieder was gelernt
3. Dein Code entspricht da sicherlich mehr den ganzen Pitfalls. Ich wollte bloß wissen, aus welchen Gründen ich meinen umändern sollte.
4. Wenn ich das richtig verstanden habe, ist die Map langsamer (auch wenns eine unordered_map ist). D.h. so etwas würde dann nicht funktionieren:T *t = new T(); shared_ptr<T> p1(t); shared_ptr<T> p2(t);Brauche ich aber eigentlich auch nicht.
-
@kk:
Gegen std::tr1::shared_ptr spricht folgendes:
1. Wenn's nicht sein muss, will ich nicht unbedingt vom TR1 abhängig sein.
2. Ich brauche einen shared_ptr, dem man die Aufsicht über das Löschen des Pointers wieder entziehen kann (für GUI-Systeme, wo Parents ihre Children selbst verwalten). So was habe ich beim std::shared_ptr nicht gefunden.P.S.: Bin gerade beim Implementieren eurer Vorschläge, melde micht dann nochmal

-
> Ich brauche einen shared_ptr, dem man die Aufsicht über das Löschen des Pointers wieder entziehen kann
Dann ist der Name "shared_ptr" IMHO unangebracht.
-
krümelkacker schrieb:
> Ich brauche einen shared_ptr, dem man die Aufsicht über das Löschen des Pointers wieder entziehen kann
Dann ist der Name "shared_ptr" IMHO unangebracht.
Dann mach mir einen besseren Vorschlag

Er ist ja meistens shared, aber eben nicht immer.
-> Die Aufsicht soll natürlich allen shared_ptr-Objekten, die den selben Pointer beinhalten, entzogen werden.EDIT: Beispiel:
shared_ptr<MyWidget> widget(new MyWidget); widget.DisableManaging(); widget->SetParent(window); //widget wird von window gemanaged //... widget->SetParent(NULL); widget.EnableManaging();
-
wxSkip schrieb:
Dann mach mir einen besseren Vorschlag
divided_ptr

oder unshared_ptr