Downcasting shared_ptr <Base> in shared_ptr <Derived>?


102

Aggiornamento: shared_ptr in questo esempio è come quello in Boost, ma non supporta shared_polymorphic_downcast (o dynamic_pointer_cast o static_pointer_cast per quella materia)!

Sto cercando di inizializzare un puntatore condiviso a una classe derivata senza perdere il conteggio dei riferimenti:

struct Base { };
struct Derived : public Base { };
shared_ptr<Base> base(new Base());
shared_ptr<Derived> derived;

// error: invalid conversion from 'Base* const' to 'Derived*'
derived = base;  

Fin qui tutto bene. Non mi aspettavo che C ++ convertisse implicitamente Base * in Derived *. Tuttavia, desidero la funzionalità espressa dal codice (ovvero, mantenere il conteggio dei riferimenti durante il downcast del puntatore di base). Il mio primo pensiero è stato quello di fornire un cast operator in Base in modo che potesse avvenire una conversione implicita in Derived (per i pedanti: controllerei che il down cast sia valido, non preoccuparti):

struct Base {
  operator Derived* ();
}
// ...
Base::operator Derived* () {
  return down_cast<Derived*>(this);
}

Beh, non ha aiutato. Sembra che il compilatore abbia completamente ignorato il mio operatore typecast. Qualche idea su come far funzionare l'assegnazione shared_ptr? Per punti extra: che tipo di tipo Base* constè? const Base*Capisco, ma Base* const? A cosa si constriferisce in questo caso?


Perché hai bisogno di un shared_ptr <Derived>, invece di shared_ptr <Base>?
Bill il

3
Perché voglio accedere a funzionalità in Derived che non sono in Base, senza clonare l'oggetto (voglio un singolo oggetto, referenziato da due puntatori condivisi). A proposito, perché gli operatori del cast non funzionano?
Lajos Nagy

Risposte:


109

Puoi usare dynamic_pointer_cast. È supportato da std::shared_ptr.

std::shared_ptr<Base> base (new Derived());
std::shared_ptr<Derived> derived =
               std::dynamic_pointer_cast<Derived> (base);

Documentazione: https://en.cppreference.com/w/cpp/memory/shared_ptr/pointer_cast

Inoltre, non consiglio di usare l'operatore cast nella classe base. Il casting implicito come questo può diventare fonte di bug ed errori.

-Update: se il tipo non è polimorfico, std::static_pointer_castpuò essere utilizzato.


4
Non ho capito dalla prima riga che non sta usando std::shared_ptr. Ma dai commenti della prima risposta ho dedotto che non sta usando boost, quindi potrebbe usare std::shared_ptr.
Massood Khaari

OK. Scusate. Dovrebbe chiarire meglio che sta utilizzando un'implementazione personalizzata.
Massood Khaari

47

Presumo tu stia usando boost::shared_ptr... Penso che tu voglia dynamic_pointer_castoshared_polymorphic_downcast .

Tuttavia, questi richiedono tipi polimorfici.

che tipo Base* constè? const Base*Capisco, ma Base* const? A cosa si constriferisce in questo caso?

  • const Base *è un puntatore mutevole a una costante Base.
  • Base const *è un puntatore mutevole a una costante Base.
  • Base * constè un puntatore costante a un mutabile Base.
  • Base const * constè un puntatore costante a una costante Base.

Ecco un esempio minimo:

struct Base { virtual ~Base() { } };   // dynamic casts require polymorphic types
struct Derived : public Base { };

boost::shared_ptr<Base> base(new Base());
boost::shared_ptr<Derived> derived;
derived = boost::static_pointer_cast<Derived>(base);
derived = boost::dynamic_pointer_cast<Derived>(base);
derived = boost::shared_polymorphic_downcast<Derived>(base);

Non sono sicuro che fosse intenzionale che il tuo esempio crei un'istanza del tipo di base e la lanci, ma serve per illustrare bene la differenza.

La static_pointer_castvolontà "basta farlo". Ciò comporterà un comportamento indefinito (un Derived*puntamento alla memoria allocata per e inizializzata da Base) e probabilmente causerà un arresto anomalo o peggio. Il conteggio dei riferimenti baseverrà incrementato.

Il dynamic_pointer_castrisultato sarà un puntatore nullo. Il conteggio dei riferimenti basenon verrà modificato.

L' shared_polymorphic_downcastavrà lo stesso risultato di un cast statico, ma si innescherà un'affermazione, piuttosto che sembrava avere successo e che porta a un comportamento indefinito. Il conteggio dei riferimenti baseverrà incrementato.

Vedi (link morto) :

A volte è un po 'difficile decidere se usare static_casto dynamic_cast, e vorresti poter avere un po' di entrambi i mondi. È risaputo che dynamic_cast ha un overhead di runtime, ma è più sicuro, mentre static_cast non ha alcun overhead, ma potrebbe non riuscire silenziosamente. Quanto sarebbe bello se potessi usarlo shared_dynamic_castnelle build di debug e shared_static_castnelle build di rilascio. Bene, una cosa del genere è già disponibile e si chiama shared_polymorphic_downcast.


Sfortunatamente, la tua soluzione dipende dalla funzionalità Boost che è stata deliberatamente esclusa dalla particolare implementazione shared_ptr che stiamo utilizzando (non chiedere perché). Per quanto riguarda la spiegazione const, ora ha molto più senso.
Lajos Nagy

3
A parte l'implementazione degli altri shared_ptrcostruttori (prendendo static_cast_tage dynamic_cast_tag), non c'è molto che puoi fare. Qualunque cosa tu faccia fuori shared_ptrnon sarà in grado di gestire il refcount. - In un design OO "perfetto" puoi sempre usare il tipo di base, e non devi mai sapere né preoccuparti di quale sia il tipo derivato, perché tutte le sue funzionalità sono esposte attraverso interfacce di classe base. Forse hai solo bisogno di ripensare al motivo per cui hai bisogno di down-cast in primo luogo.
Tim Sylvester

1
@Tim Sylvester: ma, C ++ non è un linguaggio OO "perfetto"! :-) i down-cast hanno il loro posto in un linguaggio OO non perfetto
Steve Folly

4

Se qualcuno arriva qui con boost :: shared_ptr ...

Ecco come puoi eseguire il downcast al Boost derivato shared_ptr. Supponendo che Derived erediti da Base.

boost::shared_ptr<Base> bS;
bS.reset(new Derived());

boost::shared_ptr<Derived> dS = boost::dynamic_pointer_cast<Derived,Base>(bS);
std::cout << "DerivedSPtr  is: " << std::boolalpha << (dS.get() != 0) << std::endl;

Assicurati che la classe / struttura "Base" abbia almeno una funzione virtuale. Funziona anche un distruttore virtuale.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.