Schlanke Smartpointer-Implementierung?
-
asc schrieb:
Hast du dir wirklich mal alle Fähigkeiten des shared_ptr angesehen (ich meine auch solche wie das zusammenspiel mit weak_ptr, enable_shared_from_this...)?
Ja.
asc schrieb:
Man kann auch einfachere Implementierungen machen, wenn man auf das ein oder andere verzichten kann (z.B. auf den weak_ptr), ich hatte vor längeren in einer Firma händisch einen solchen in Anlehnung auf eine Implementierung von Scott Meyers geschrieben. Die war zwar schlanker, konnte aber auch wesentlich weniger (Mit einer Realisierung die stark an eine doppelt verkettete Liste erinnert hat, ohne einer Referenzzählung).
Also reference linked Smartpointer.
-
In jedem seinem Posting merkt man, wie Pi nach Anerkennung bettelt.

-
Wenn man möglichst effizient und schlank bleiben wollen will, würde ich einfach eine Basis für reference-counted objects schreiben:
class reference_counted { private: std::atomic_uint m_ref; public: void inc_ref() { ++m_ref; } void dec_ref() { if(std::atomic_fetch_sub(&m_ref, 1) == 0) delete this; } };Und davon halt alle Klassen ableiten + einen smart pointer schreiben der nur inc_ref/dec_ref aufruft. Fertig.
-
Ich finde die Idee blöd, das Objekt die Lebenszeit managen zu lassen. Auf die hat es schlielich keinen Einfluss. Des Weiteren zwingst du damit den Instanzen einen Refcounter auf, auch wenn das Ding am Stack liegt.
-
Ethon schrieb:
Wenn man möglichst effizient und schlank bleiben wollen will, würde ich einfach eine Basis für reference-counted objects schreiben:
class reference_counted { private: std::atomic_uint m_ref; public: void inc_ref() { ++m_ref; } void dec_ref() { if(std::atomic_fetch_sub(&m_ref, 1) == 0) delete this; } };Und davon halt alle Klassen ableiten + einen smart pointer schreiben der nur inc_ref/dec_ref aufruft. Fertig.
Wenn man das unsinnige Ableiten weglässt, hat man wieder
shared_ptr.
Außerdem hast du vergessen, den Destruktor virtuell zu machen.
delete thisist ohnehin sehr unschön.
-
314159265358979 schrieb:
Ich finde die Idee blöd, das Objekt die Lebenszeit managen zu lassen. Auf die hat es schlielich keinen Einfluss.
D.h. ein std::vector sollte auch niemals seinen Inhalt managen, denn auf diesen hat er schliesslich keinen Einfluss.
Saudummes Argument.Intrusive Ref-Counting ist toll.
-
Wenn man das unsinnige Ableiten weglässt, hat man wieder shared_ptr.
shared_ptr kann funktionieren OHNE den ref-count auf dem Heap abzulegen?
Außerdem hast du vergessen, den Destruktor virtuell zu machen.
War ja nur ein Snippet.

delete this ist ohnehin sehr unschön.
Wenn es kontrolliert passiert, wieso?
Des Weiteren zwingst du damit den Instanzen einen Refcounter auf, auch wenn das Ding am Stack liegt.
Ich gehe mal davon aus, dass man einzelne Objekte nur auf dem Heap erzeugt, wenn sie primär da Sinn machen. Also in erster Linie für Laufzeit-Polymorphie, Factories etc.
-
Ethon schrieb:
Wenn man das unsinnige Ableiten weglässt, hat man wieder shared_ptr.
shared_ptr kann funktionieren OHNE den ref-count auf dem Heap abzulegen?
Nein, intrusive aber auch nicht. Im Speicher sehen beide gleich aus (selbstverständlich mit
make_shared).Ethon schrieb:
delete this ist ohnehin sehr unschön.
Wenn es kontrolliert passiert, wieso?
Weil es unlogisch ist, dass ein Objekt sich selbst zerstört. Es hat sich ja auch nicht selbst erstellt.
-
Ethon schrieb:
Wenn man das unsinnige Ableiten weglässt, hat man wieder shared_ptr.
shared_ptr kann funktionieren OHNE den ref-count auf dem Heap abzulegen?
shared_ptr kann - wenn man make_shared verwendet - ohne zusätzliche Allokation auskommen. Objekt + Deleter + Counter werden dann alle in einen einzigen Speicherblock reingepackt.
Das hat natürlich den Nebeneffekt, dass der Speicher für das gesamte Objekt + Counter erst freigegeben wird sobald der letzte weak_ptr darauf verschwindet.
-
hustbaer schrieb:
D.h. ein std::vector sollte auch niemals seinen Inhalt managen, denn auf diesen hat er schliesslich keinen Einfluss.
Saudummes Argument.Du bist echt dumm wie Stroh. Natürlich besitzt der vector seine Elemente, aber nicht sich selbst.
-
Hihi. Ja, wenn DU das sagst.
-
TyRoXx schrieb:
Ethon schrieb:
Wenn man möglichst effizient und schlank bleiben wollen will, würde ich einfach eine Basis für reference-counted objects schreiben:
class reference_counted { private: std::atomic_uint m_ref; public: void inc_ref() { ++m_ref; } void dec_ref() { if(std::atomic_fetch_sub(&m_ref, 1) == 0) delete this; } };Und davon halt alle Klassen ableiten + einen smart pointer schreiben der nur inc_ref/dec_ref aufruft. Fertig.
Wenn man das unsinnige Ableiten weglässt, hat man wieder
shared_ptr.
Außerdem hast du vergessen, den Destruktor virtuell zu machen.
delete thisist ohnehin sehr unschön.Ableiten ist schon sinnvoll um Code-Bloat zu vermeiden. Allerdings ist es immer noch Aufgabe des Smartpointers und nicht des Referenzcounters, dafür zur sorgen, dass das Objekt am Ende freigegeben wird. Und dann benötigt man auch keinen virtuellen Destruktor. atomic_fetch_sub gibt übrigens den alten Wert des Counters zurück...
-
314159265358979 schrieb:
hustbaer schrieb:
D.h. ein std::vector sollte auch niemals seinen Inhalt managen, denn auf diesen hat er schliesslich keinen Einfluss.
Saudummes Argument.Du bist echt dumm wie Stroh. Natürlich besitzt der vector seine Elemente, aber nicht sich selbst.
Zeig doch ein bisschen respekt.
Worum es geht ist Intrusiv-Verhalten. Das ist in vielen Situationen ziemlich ideal. zB wenn man eine Resource eh nur per Smartpointer verwalten wird. make_shared macht ja auch nichts viel anderes als intrusiv zu sein, nur dass make_shared halt ein boeser Hack ist.
-
Shade Of Mine schrieb:
Zeig doch ein bisschen respekt.
Wenn er sich absichtlich dumm stellt...
Shade Of Mine schrieb:
Worum es geht ist Intrusiv-Verhalten. Das ist in vielen Situationen ziemlich ideal. zB wenn man eine Resource eh nur per Smartpointer verwalten wird.
Fahre fort.
Shade Of Mine schrieb:
make_shared macht ja auch nichts viel anderes als intrusiv zu sein, nur dass make_shared halt ein boeser Hack ist.
Wie bitte? Also entweder, du hast make_shared nicht verstanden, oder ich dich nicht. Erklär mal genauer, wie du das meinst.
-
314159265358979 schrieb:
Shade Of Mine schrieb:
Zeig doch ein bisschen respekt.
Wenn er sich absichtlich dumm stellt...
Ich stell mich nicht absichtlich dumm. Dafür bist du gerade wieder dabei in den "ich weiss was ich weiss was ich weiss was ich weiss was ich weiss was!!!!" Modus zu schalten, der dir dann jedes mal das Gehirn rausschmilzt.
Zurück zum Thema:
Nur weil das Objekt den Reference-Count "enthält" heisst dass noch lange nicht dass es "sich selbst besitzt".
-
314159265358979 schrieb:
Shade Of Mine schrieb:
Worum es geht ist Intrusiv-Verhalten. Das ist in vielen Situationen ziemlich ideal. zB wenn man eine Resource eh nur per Smartpointer verwalten wird.
Fahre fort.
Was gibts da noch zu erklaeren? Intrusiv-Verhalten spart allokationen und erhoeht Cache-Lokalitaet. Das ist ne gute Sache.
Shade Of Mine schrieb:
make_shared macht ja auch nichts viel anderes als intrusiv zu sein, nur dass make_shared halt ein boeser Hack ist.
Wie bitte? Also entweder, du hast make_shared nicht verstanden, oder ich dich nicht. Erklär mal genauer, wie du das meinst.[/quote]
Dirty Hack ist vielleicht uebertrieben - aber es hat seine Kosten dass nur eine Allokation statt 2 stattfindet. make_shared allokiert einen Block an Speicher und erstellt dort drin 2 Objekte. Das bedeutet, dass wir zB keinen eigenen allocator fuer unsere Klasse verwenden koennen, wir koennen auch deshalb keinen eigenen deleter verwenden. Wir koennen das Objekt auch nicht aus dem shared_ptr raus nehmen, wenn wir es nicht mehr laenger dort verwalten lassen wollen,...
make_shared hat viele Nachteile. Natuerlich sind diese Nachteile in fast jeder Situation durch den Performance Gewinn aufgehoben - aber ganz so toll ist make_shared doch nicht.
Eine andere Loesung das make_shared Problem zu loesen ist die Klasse direkt intrusiv zu verwenden. Das wuerde viele Probleme von make_shared loesen (und natuerlich auch selber wieder andere haben).
Aber wir diskutieren hier nicht wirklich ob intrusive Datenstrukturen sinnvoll sind, oder?
-
Shade Of Mine schrieb:
make_shared hat viele Nachteile. Natuerlich sind diese Nachteile in fast jeder Situation durch den Performance Gewinn aufgehoben - aber ganz so toll ist make_shared doch nicht.
Welche vielen Nachteile sind das? Die Sache mit dem verzögertem Freigeben des Speichers im Falle von weak_ptrs könnte man wahrscheinlich als Nachteil auffassen. Aber wann spielt der denn in der Praxis überhaupt eine Rolle? Doch in fast keiner Situation, würde ich mal behaupten. Was gibt's denn noch an Nachteilen?
-
krümelkacker schrieb:
Was gibt's denn noch an Nachteilen?
Sagt er doch:
Shade Of Mine schrieb:
aber es hat seine Kosten dass nur eine Allokation statt 2 stattfindet. make_shared allokiert einen Block an Speicher und erstellt dort drin 2 Objekte. Das bedeutet, dass wir zB keinen eigenen allocator fuer unsere Klasse verwenden koennen, wir koennen auch deshalb keinen eigenen deleter verwenden. Wir koennen das Objekt auch nicht aus dem shared_ptr raus nehmen, wenn wir es nicht mehr laenger dort verwalten lassen wollen,...
-
Es gibt meines Wissens noch allocate_shared, was einen eigenen Allocator entgegen nimmt. Das "Herauslösen" aus einem shared_ptr ist so oder so nicht vorgesehen und würde ich als Hack einstufen.
-
20.7.2.2.6 shared_ptr creation [util.smartptr.shared.create]
template<class T, class... Args> shared_ptr<T> make_shared(Args&&... args); template<class T, class A, class... Args> shared_ptr<T> allocate_shared(const A& a, Args&&... args);Requires: The expression ::new (pv) T(std::forward<Args>(args)...), where pv has type void* and points to storage suitable to hold an object of type T, shall be well formed. A shall be an allocator (17.6.3.5). The copy constructor and destructor of A shall not throw exceptions.
Effects: Allocates memory suitable for an object of type T and constructs an object in that memory via the placement new expression ::new (pv) T(std::forward<Args>(args)...). The template allocate_shared uses a copy of a to allocate memory. If an exception is thrown, the functions have no effect.
Returns: A shared_ptr instance that stores and owns the address of the newly constructed object of type T.
Postconditions: get() != 0 && use_count() == 1
Throws: bad_alloc, or an exception thrown from A::allocate or from the constructor of T.
Remarks: Implementations are encouraged, but not required, to perform no more than one memory allocation. [ Note: This provides efficiency equivalent to an intrusive smart pointer. — end note ]
[Note: These functions will typically allocate more memory than sizeof(T) to allow for internal bookkeeping structures such as the reference counts. — end note ]