Hallo Leute,
ich versteh nicht ganz, warum der Synthax von unique_ptr und shared_ptr
bei der Verwendung eines custom-deleter voneinander abweichen. Folgender
Code (ausschnitt):
1. tip top
2. so hätte ich es gerne, aber funktioniert nicht.
3. doof, aber funktioniert
Ich weiss, das ist mehr ein ästhetisches Problem, aber da die Klasse Foo
in wahrheit mehr als ein Template Parameter hat, wirklich umständlich
und unübersichtlich. Vorallem versteh ich nicht, wieso mit shared_ptr
funktioniert, mit unique_ptr aber nicht :( Oder was mach ich falsch?
viele grüsse
Der shared_ptr macht da vermutlich Type Erasure, was einem die explizite
Angabe erspart, aber den auch ineffizienter macht.
Versuch vielleicht besser, den deleter komplett loszuwerden? Pack deine
"Shmu" Klasse in eine andere hinein, die im Destruktor deine
delete-Routine durchführt.
Rasputin schrieb:> Hallo Leute,>> ich versteh nicht ganz, warum der Synthax von unique_ptr und shared_ptr> bei der Verwendung eines custom-deleter voneinander abweichen. Folgender> Code (ausschnitt):>>
> 1. tip top> 2. so hätte ich es gerne, aber funktioniert nicht.> 3. doof, aber funktioniert>> Ich weiss, das ist mehr ein ästhetisches Problem, aber da die Klasse Foo> in wahrheit mehr als ein Template Parameter hat, wirklich umständlich> und unübersichtlich. Vorallem versteh ich nicht, wieso mit shared_ptr> funktioniert, mit unique_ptr aber nicht :( Oder was mach ich falsch?>> viele grüsse
Abgesehen davon, dass der Zweck hier recht fragwürdig ist:
2) kann nicht funktionieren wegen Typinkompatibilität, deswegen ja 3)
(hast Du ja selbst herausgefunden ...)
Schon mal was von "auto" gehört? (Achtung: die Regeln zur Typinferenz
sind manchmal nicht das, was man als naiver Leser erwartet).
Dr. Sommer schrieb:> Der shared_ptr macht da vermutlich Type Erasure, was einem die explizite> Angabe erspart, aber den auch ineffizienter macht.
Type Erasure gibt es bei Java, aber nicht bei C++.
Wilhelm M. schrieb:> Type Erasure gibt es bei Java, aber nicht bei C++.
Doch, aber es ist nicht ganz das Gleiche. Bei C++ implementiert man das
explizit. std::function macht genau das z.B.:
egal wovon die Ziel Funktion ein Member ist und wie viele gebundene
Parameter sie hat - man kann sie alle über den selben std::function Typ
ansprechen. Der innere Typ wurde erased.
Dr. Sommer schrieb:> Wilhelm M. schrieb:>> Type Erasure gibt es bei Java, aber nicht bei C++.>> Doch, aber es ist nicht ganz das Gleiche. Bei C++ implementiert man das> explizit. std::function macht genau das z.B.:> egal wovon die Ziel Funktion ein Member ist und wie viele gebundene> Parameter sie hat - man kann sie alle über den selben std::function Typ> ansprechen.
Du bezeichnest eine Wrapper-Klasse als type rasure ...
> Der innere Typ wurde erased.
Nein, der innere Typ liegt in dem Template e.g. std::function oder
boost::variant immer noch vor.
Wilhelm M. schrieb:>> Der innere Typ wurde erased.>> Nein, der innere Typ liegt in dem Template e.g. std::function oder> boost::variant immer noch vor.
Schon klar, allerdings nicht mehr sichtbar von außen. Einem
std::shared_ptr<Shmu> sieht man im Typ daher nicht mehr an was für ein
Custom Deleter verwendet wurde.
Wilhelm M. schrieb:> Du bezeichnest eine Wrapper-Klasse als type rasure ...
Es ist in gewisser Hinsicht eine spezielle Wrapper-Klasse, ja.
Wilhelm M. schrieb:> Nein, der innere Typ liegt in dem Template e.g. std::function oder> boost::variant immer noch vor.
Ja. Aber er ist nach außen nicht mehr sichtbar, und std::function kann
ohne den Typ zu kennen instanziert werden.
Aber bitte, wenn du mir nicht glaubst, google doch einfach nach "C++
Type Erasure". Ich habe mir das nicht ausgedacht.
z.B. https://akrzemi1.wordpress.com/2013/11/18/type-erasure-part-i/
Warum schreibst du nicht einfach auto?
Der Code sieht aber generell sehr fraglich aus, ich nehme einfach mal an
dass die Vereinfachung den Sinn völlig entstellt ...
Dr. Sommer schrieb:> Aber bitte, wenn du mir nicht glaubst, google doch einfach nach "C++> Type Erasure". Ich habe mir das nicht ausgedacht.>> z.B. https://akrzemi1.wordpress.com/2013/11/18/type-erasure-part-i/
Den Artikel kenne ich schon.
Und trotzdem bin ich nicht damit einverstanden, das als Type-Erasure zu
bezeichnen ... auch wenn manche Leute das Pattern so bezeichnen, finde
ich es irreführend. Da erlaube ich mir eine eigene Meinung.
Danke für eure Beiträge.
Das mit dem "Type Erasure" muss ich mal in ruhe studieren.
auto verwende ich nicht gerne. Ich mag es gerne, wenn der Typ explizit
angegeben ist. Das finde ich klarer (wenn ich Monate später nochmals in
den Code schaue).
Also im Prinzip kann man sagen, dass es aus Performance-gründen so
gelöst wurde. Ich schätze mal, dass ein unique_ptr gegenüber einem rohen
Pointer keinen Performancenachteil hat? shared_ptr ist hingegen nicht so
auf Performance getrimmt, da es durch die Referenzzählung sowieso
overhead mitbringt. Richtig?
Genau so ist es. shared_ptr sollte man allgemein eher etwas sparsam
verwenden und nicht überall hin übergeben eben weil es nicht so
effizient ist (sein kann!).
Das ist nicht wie Java wo man beliebig Referenzen effizient übergeben
kann und die Garbage Collection sorgt dafür dass es passt; mit
shared_ptr muss man schon auch etwas nachdenken.
Dr. Sommer schrieb:> Genau so ist es. shared_ptr sollte man allgemein eher etwas sparsam> verwenden und nicht überall hin übergeben eben weil es nicht so> effizient ist (sein kann!).> Das ist nicht wie Java wo man beliebig Referenzen effizient übergeben> kann und die Garbage Collection sorgt dafür dass es passt; mit> shared_ptr muss man schon auch etwas nachdenken.
Es gibt ja drei Zeiger Arten:
1) alleinige Eigentümerschaft std::unique_ptr
2) geteilte Eigentümerschaft: std::shared_ptr
3) Benutzungsrelation: std::observer_ptr oder roher Zeiger
Hat man eine Funktion als Senke, so sollte sie als Parameter einen
unique_ptr per-value haben: man ist gezwungen zu moven -> man übergibt
klar die Eigentümerschaft. Aufwand: ein pointer swap.
Eine (Element-)Funktion will die Lebensdauer ggf. verlängern:
Parameterübergabe als shared_ptr per-value: Aufwand: pointer-copy und
inkrement (als RMW-atomic)
Eine Funktion will eine Ressource nur benutzen: Parameterübergabe
raw-pointer per-value: Aufwand: pointer-copy (das get() aus dem
SmartPointer wird wohl wegoptimiert). Um dies noch klarer zum Ausdruck
zu bringen, gibt es den non-owning std::observer_ptr (als documenting
type)
Wilhelm M. schrieb:> Eine (Element-)Funktion will die Lebensdauer ggf. verlängern:> Parameterübergabe als shared_ptr per-value: Aufwand: pointer-copy und> inkrement (als RMW-atomic)
Sollte man nicht eine const reference auf den shared_ptr übergeben
solange man nicht wirklich eine Kopie benötigt? Oder was genau meinst du
mit Lebensdauer verlängern?
mh schrieb:> Wilhelm M. schrieb:>> Eine (Element-)Funktion will die Lebensdauer ggf. verlängern:>> Parameterübergabe als shared_ptr per-value: Aufwand: pointer-copy und>> inkrement (als RMW-atomic)>> Sollte man nicht eine const reference auf den shared_ptr übergeben> solange man nicht wirklich eine Kopie benötigt? Oder was genau meinst du> mit Lebensdauer verlängern?
const-ref sagt eher aus, dass es ggf. nicht klar ist, ob auch die
Lebendauer verlängert wird. Eine Kopie kann ggf. später anderswo gemacht
werden.
Lebensdauer verlängern heisst, dass eine Funktion den shared-ptr kopiert
und ggf. anderswo / in einem anderen Objekt speichert. Diese Objekt
verlängert damit die Lebensdauer des Objektes, das per shared_ptr
übergeben wurde.
Ist das nicht der Fall, sollte man nur den raw-pointer (besser
observer_ptr) übergeben.