Übergeben von shared_ptr <Derived> als shared_ptr <Base>

90

Was ist die beste Methode, um shared_ptreinen abgeleiteten Typ an eine Funktion zu übergeben, die shared_ptreinen Basistyp verwendet?

Ich übergebe im Allgemeinen shared_ptrs als Referenz, um eine unnötige Kopie zu vermeiden:

int foo(const shared_ptr<bar>& ptr);

aber das funktioniert nicht, wenn ich versuche, so etwas zu tun

int foo(const shared_ptr<Base>& ptr);

...

shared_ptr<Derived> bar = make_shared<Derived>();
foo(bar);

ich könnte benutzen

foo(dynamic_pointer_cast<Base, Derived>(bar));

Dies scheint jedoch aus zwei Gründen nicht optimal zu sein:

  • A dynamic_castscheint für eine einfache Ableitung von der Basis zur Basis etwas übertrieben zu sein.
  • So wie ich es verstehe, dynamic_pointer_castwird eine Kopie (wenn auch eine temporäre) des Zeigers erstellt, die an die Funktion übergeben wird.

Gibt es eine bessere Lösung?

Update für die Nachwelt:

Es stellte sich heraus, dass eine Header-Datei fehlte. Auch was ich hier versucht habe, wird als Antimuster angesehen. Allgemein,

  • Funktionen, die sich nicht auf die Lebensdauer eines Objekts auswirken (dh das Objekt bleibt für die Dauer der Funktion gültig), sollten eine einfache Referenz oder einen Zeiger verwenden, z int foo(bar& b).

  • Funktionen, die ein Objekt verbrauchen (dh die Endbenutzer eines bestimmten Objekts sind), sollten einen unique_ptrBy-Wert annehmen , z int foo(unique_ptr<bar> b). Anrufer sollten std::moveden Wert in die Funktion einfließen lassen .

  • Funktionen, die die Lebensdauer eines Objekts verlängern, sollten einen shared_ptrBy-Wert annehmen , z int foo(shared_ptr<bar> b). Die übliche Beratung zu vermeiden zirkuläre Referenzen gilt.

Weitere Informationen finden Sie im Back to Basics-Vortrag von Herb Sutter .

Matt Kline
quelle
8
Warum willst du ein bestehen shared_ptr? Warum keine konstante Referenz von bar?
IPC
2
Jede dynamicBesetzung wird nur zum Downcasting benötigt. Außerdem sollte das Übergeben des abgeleiteten Zeigers einwandfrei funktionieren. Es wird ein neues shared_ptrmit demselben Refcount erstellt (und erhöht) und ein Zeiger auf die Basis, der dann an die const-Referenz gebunden wird. Da Sie jedoch bereits eine Referenz nehmen, verstehe ich nicht, warum Sie überhaupt eine nehmen möchten shared_ptr. Nehmen Sie ein Base const&und rufen Sie an foo(*bar).
Xeo
@Xeo: Das Übergeben des abgeleiteten Zeigers (dh foo(bar)) funktioniert zumindest in MSVC 2010 nicht.
Matt Kline
1
Was meinst du mit "funktioniert offensichtlich nicht"? Der Code wird kompiliert und verhält sich korrekt. Fragen Sie, wie Sie vermeiden können, ein temporäres Element zu erstellen shared_ptr, das an die Funktion übergeben wird? Ich bin mir ziemlich sicher, dass es keinen Weg gibt, das zu vermeiden.
Mike Seymour
1
@ Seth: Ich bin anderer Meinung. Ich denke, es gibt einen Grund, einen gemeinsam genutzten Zeiger als Wert zu übergeben, und es gibt kaum einen Grund, einen gemeinsam genutzten Zeiger als Referenz zu übergeben (und das alles, ohne nicht benötigte Kopien zu befürworten). Begründung hier stackoverflow.com/questions/10826541/…
R. Martinho Fernandes

Antworten:

47

Obwohl Baseund Derivedsind kovariant und rohe Zeiger auf sie werden entsprechend handeln shared_ptr<Base>und shared_ptr<Derived>sind nicht kovariant. Dasdynamic_pointer_cast ist der richtige und einfachste Weg , um dieses Problem zu behandeln.

( Bearbeiten: static_pointer_cast wäre besser geeignet, da Sie von abgeleitet zu Basis übertragen, was sicher ist und keine Laufzeitprüfungen erfordert. Siehe Kommentare unten.)

Wenn Ihre foo()Funktion jedoch nicht an der Verlängerung der Lebensdauer teilnehmen möchte (oder vielmehr am gemeinsamen Besitz des Objekts teilnehmen möchte), ist es am besten, a zu akzeptieren const Base&und das zu dereferenzieren, shared_ptrwenn Sie es an übergeben foo().

void foo(const Base& base);
[...]
shared_ptr<Derived> spDerived = getDerived();
foo(*spDerived);

Da shared_ptrTypen nicht kovariant sein können, gelten die Regeln für implizite Konvertierungen über kovariante Rückgabetypen hinweg nicht, wenn Typen von zurückgegeben werden shared_ptr<T>.

Bret Kuhns
quelle
38
Sie sind nicht kovariant, können aber shared_ptr<Derived>implizit in konvertiert werden shared_ptr<Base>, sodass der Code ohne Casting-Spielereien funktionieren sollte.
Mike Seymour
9
Ähm, shared_ptr<Ty>hat einen Konstruktor, der a nimmt shared_ptr<Other>und die entsprechende Konvertierung durchführt, wenn er Ty*implizit in konvertierbar ist Other*. Und wenn eine Besetzung benötigt wird, static_pointer_castist hier die passende nicht dynamic_pointer_cast.
Pete Becker
Stimmt, aber nicht mit seinem Referenzparameter, wie in der Frage. Er würde trotzdem eine Kopie machen müssen. Aber wenn er refs tos verwendet, shared_ptrum die Referenzzählung zu vermeiden, gibt es wirklich keinen guten Grund, überhaupt a zu verwenden shared_ptr. Es ist am besten, const Base&stattdessen zu verwenden .
Bret Kuhns
@PeteBecker Siehe meinen Kommentar an Mike zum Konvertierungskonstruktor. Ich wusste ehrlich gesagt nichts davon static_pointer_cast, danke.
Bret Kuhns
1
@ TanveerBadar Nicht sicher. Vielleicht konnte dies 2012 nicht kompiliert werden? (speziell mit Visual Studio 2010 oder 2012). Aber Sie haben absolut Recht, der Code von OP sollte unbedingt kompiliert werden, wenn die vollständige Definition einer / öffentlich abgeleiteten / Klasse für den Compiler sichtbar ist.
Bret Kuhns
26

Dies geschieht auch, wenn Sie vergessen haben, die öffentliche Vererbung für die abgeleitete Klasse anzugeben , dh wenn Sie wie ich Folgendes schreiben:

class Derived : Base
{
};
Schäferhund
quelle
classist für Vorlagenparameter; structdient zum Definieren von Klassen. (Dies ist höchstens 45% ein Witz.)
Davis Herring
11

Klingt so, als würden Sie sich zu sehr anstrengen. shared_ptrist billig zu kopieren; Das ist eines seiner Ziele. Wenn Sie sie als Referenz weitergeben, wird nicht wirklich viel erreicht. Wenn Sie keine Freigabe wünschen, übergeben Sie den Rohzeiger.

Es gibt jedoch zwei Möglichkeiten, die ich mir auf den ersten Blick vorstellen kann:

foo(shared_ptr<Base>(bar));
foo(static_pointer_cast<Base>(bar));
Pete Becker
quelle
9
Nein, sie sind nicht billig zu kopieren, sie sollten nach Möglichkeit als Referenz übergeben werden.
Seth Carnegie
6
@SethCarnegie - Hat Herb Ihren Code profiliert, um festzustellen, ob das Übergeben von Werten ein Engpass war?
Pete Becker
24
@ SethCarnegie - das beantwortet nicht die Frage, die ich gestellt habe. Und für das, was es wert ist, habe ich die shared_ptrImplementierung geschrieben, die Microsoft liefert.
Pete Becker
6
@ SethCarnegie - Sie haben die Heuristik rückwärts. Handoptimierungen sollten im Allgemeinen nur durchgeführt werden, wenn Sie nachweisen können, dass sie benötigt werden.
Pete Becker
20
Es ist nur eine "vorzeitige" Optimierung, wenn Sie daran arbeiten müssen. Ich sehe kein Problem darin, effiziente Redewendungen gegenüber ineffizienten zu verwenden, unabhängig davon, ob dies in einem bestimmten Kontext einen Unterschied macht oder nicht.
Mark Ransom
11

Überprüfen Sie auch, ob sich die #includeHeader-Datei mit der vollständigen Deklaration der abgeleiteten Klasse in Ihrer Quelldatei befindet.

Ich hatte dieses Problem. Das std::shared<derived>würde nicht besetzen std::shared<base>. Ich hatte beide Klassen vorwärts deklariert, damit ich Zeiger auf sie halten konnte, aber weil ich den nicht hatte, konnte #includeder Compiler nicht sehen, dass eine Klasse von der anderen abgeleitet war.

Phil Rosenberg
quelle
1
Wow, ich habe es nicht erwartet, aber das hat es für mich behoben. Ich war besonders vorsichtig und habe Header-Dateien nur dort eingefügt, wo ich sie brauchte, sodass einige von ihnen nur in Quelldateien enthalten waren, und sie wie gesagt in Headern deklariert.
Jigglypuff
Blöder Compiler ist dumm. Das war mein Problem. Danke dir!
Tanveer Badar