Forum: PC-Programmierung Inkonsistenz in C++ smart pointer


von Rasputin (Gast)


Lesenswert?

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
template <typename T>
2
class Foo {
3
  class deleter_t;
4
public:
5
  typedef std::unique_ptr<T, deleter_t> unique_ptr_t;
6
  typedef std::shared_ptr<T> shared_ptr_t;
7
8
  unique_ptr_t getUniquePtr() {
9
    return unique_ptr_t(this->getInstance(), this->deleter);
10
  }
11
12
  shared_ptr_t getSharedPtr() {
13
    return shared_ptr_t(this->getInstance(), this->deleter);
14
  }
15
private:
16
  deleter_t deleter;
17
}
18
19
Foo<Shmu> foo;
20
std::shared_ptr<Shmu> ptr1 = foo.getSharedPtr(); // 1
21
std::unique_ptr<Shmu> ptr2 = foo.getUniquePtr(); // 2
22
Foo<Shmu>::unique_ptr_t ptr3 = foo.getUniquePtr(); // 3
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

von Dr. Sommer (Gast)


Lesenswert?

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.

von Wilhelm M. (wimalopaan)


Lesenswert?

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
> ...
2
> Foo<Shmu> foo;
3
> std::shared_ptr<Shmu> ptr1 = foo.getSharedPtr(); // 1
4
> std::unique_ptr<Shmu> ptr2 = foo.getUniquePtr(); // 2
5
> Foo<Shmu>::unique_ptr_t ptr3 = foo.getUniquePtr(); // 3
6
>
> 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).

von Wilhelm M. (wimalopaan)


Lesenswert?

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++.

von Dr. Sommer (Gast)


Lesenswert?

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.

von Wilhelm M. (wimalopaan)


Lesenswert?

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.

von Sebastian V. (sebi_s)


Lesenswert?

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.

von Dr. Sommer (Gast)


Lesenswert?

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/

von Sven B. (scummos)


Lesenswert?

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 ...

Beitrag #5059586 wurde vom Autor gelöscht.
von Wilhelm M. (wimalopaan)


Lesenswert?

Sven B. schrieb:
> Warum schreibst du nicht einfach auto?

Hatte ich oben auch schon vorgechlagen!

von Wilhelm M. (wimalopaan)


Lesenswert?

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.

: Bearbeitet durch User
von Rasputin (Gast)


Lesenswert?

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?

von Dr. Sommer (Gast)


Lesenswert?

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.

von Wilhelm M. (wimalopaan)


Lesenswert?

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)

von mh (Gast)


Lesenswert?

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?

von Wilhelm M. (wimalopaan)


Lesenswert?

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.

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.