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


Announcement: there is an English version of this forum on EmbDev.net. Posts you create there will be displayed on Mikrocontroller.net and EmbDev.net.
von Rasputin (Gast)


Bewertung
0 lesenswert
nicht 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)


Bewertung
0 lesenswert
nicht 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)


Bewertung
0 lesenswert
nicht 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)


Bewertung
0 lesenswert
nicht 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)


Bewertung
-1 lesenswert
nicht 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)


Bewertung
1 lesenswert
nicht 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)


Bewertung
-1 lesenswert
nicht 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)


Bewertung
0 lesenswert
nicht 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)


Bewertung
0 lesenswert
nicht 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)


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

Hatte ich oben auch schon vorgechlagen!

von Wilhelm M. (wimalopaan)


Bewertung
0 lesenswert
nicht 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)


Bewertung
0 lesenswert
nicht 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)


Bewertung
0 lesenswert
nicht 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)


Bewertung
1 lesenswert
nicht 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)


Bewertung
0 lesenswert
nicht 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)


Bewertung
0 lesenswert
nicht 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.

Antwort schreiben

Die Angabe einer E-Mail-Adresse ist freiwillig. Wenn Sie automatisch per E-Mail über Antworten auf Ihren Beitrag informiert werden möchten, melden Sie sich bitte an.

Wichtige Regeln - erst lesen, dann posten!

  • Groß- und Kleinschreibung verwenden
  • Längeren Sourcecode nicht im Text einfügen, sondern als Dateianhang

Formatierung (mehr Informationen...)

  • [c]C-Code[/c]
  • [code]Code in anderen Sprachen, ASCII-Zeichnungen[/code]
  • [math]Formel in LaTeX-Syntax[/math]
  • [[Titel]] - Link zu Artikel
  • Verweis auf anderen Beitrag einfügen: Rechtsklick auf Beitragstitel,
    "Adresse kopieren", und in den Text einfügen




Bild automatisch verkleinern, falls nötig
Bitte das JPG-Format nur für Fotos und Scans verwenden!
Zeichnungen und Screenshots im PNG- oder
GIF-Format hochladen. Siehe Bildformate.
Hinweis: der ursprüngliche Beitrag ist mehr als 6 Monate alt.
Bitte hier nur auf die ursprüngliche Frage antworten,
für neue Fragen einen neuen Beitrag erstellen.

Mit dem Abschicken bestätigst du, die Nutzungsbedingungen anzuerkennen.