2015-05-18 14 views
7

Cosa c'è di male nel fare qualcosa del genere?estendere shared_ptr per ereditarietà

class myclass : public std::shared_ptr<myotherclass> { 
    // some code, an allocation is never done 
    std::string get_info() { 
      if(*this != nullptr) return "<info>" + (this->info * 3) + "</info>"; 
      else return ""; 
    } 
}; 

quando nessuna assegnazione è effettuata nella classe --- è solo per fornire alcune decorazioni come sopra?

+2

Cosa c'è di così buono nel fare qualcosa del genere? – Drax

+0

Nella maggior parte dei casi dovresti essere in grado di descrivere la relazione di ereditarietà con la fase inglese "è un". Quindi nel caso precedente quello che stai dicendo è: myclass "è un" std :: shared_ptr . Questo probabilmente non è quello che intendi. –

+0

Non è "cattivo" tanto quanto "strano". Sicuramente questo dovrebbe essere un membro di 'myotherclass' stesso (o forse un non-membro che agisce su' myotherclass'), non qualcosa di imbullonato a qualche specifico tipo di puntatore intelligente? –

risposta

7

In linea di massima è consentito derivare dalle classi STL, vedere here e here. Tuttavia, è necessario essere consapevoli del fatto che non si dovrebbe lavorare con un puntatore alla classe base, ad esempio uno std::shared_ptr<myotherclass>* in questo caso.

Quindi questo e loro varianti dovrebbe essere vietato:

std::shared_ptr<myotherclass>* ptr = new myclass(/* ... */); 

... ma ha accettato, che sembra un po 'sintetico.

Perché è vietato? Perché le classi STL non hanno un distruttore virtuale. Pertanto, quando si desidera che la classe allocata sia delete, la parte derivata rimane. Ciò a sua volta invoca undefined behaviour ed eventualmente crea una perdita di memoria, anche se non si dispone di alcune allocazioni nella classe derivata.

Per fare ciò, una possibilità è quella di ricavare privatamente da shared_ptr:

class myclass : private std::shared_ptr<myotherclass> {}; 
       ^^^^^^^ 

Questo però potrebbe portare a problemi con la compatibilità binaria, vedere i commenti a this answer.

Alla mano, anche se il primo è consentito, si può andare con meno soggetto a errori e sia per uso composizione, in cui si effettua la shared_ptr un membro di myclass ed esporre la funzionalità richiesta (con l'inconveniente che a volte hanno per esporre molto). Oppure puoi impostare una funzione autonoma che fa quello che vuoi ... Lo so che lo sapevi ;-)

+1

'std :: shared_ptr * ptr = ...' non ha senso. Se un distruttore virtuale diventa necessario per una classe RAII, qualcosa è già stato gravemente abusato. – Potatoswatter

+0

@davidhigh - risposta molto completa: mostra i limiti dell'usabilità ("non si dovrebbe lavorare con un puntatore alla classe base"), punta ai riferimenti (in uno di essi ho potuto vedere esattamente dove sulla specifica stavo facendo un errore) e dimostra la comprensione di ciò che il question maker stava cercando di ottenere ("esponi la funzionalità richiesta (con l'inconveniente che a volte devi esporre molto) oppure puoi impostare una funzione standalone"). – ribamar

+0

@Potatoswatter: 'std :: shared_ptr * ptr = ...' * è * un'assurdità, ovviamente ... ed in effetti è così assurdo che nessuno lo farà mai per errore. – davidhigh

1

Dal momento che non lo farai mai manualmente delete (e non dovresti mai manualmente, delete, che è piuttosto il punto di shared_ptr in primo luogo), i distruttori virtuali non sono davvero un problema.

Alcuni problemi di interoperabilità possono tuttavia presentarsi.

  1. Ottieni la classe derivata solo quando ne crei istanze specifiche. Quando ottieni shared_ptr da un posto come get_shared_from_this, non includerà il tuo info.

  2. I modelli di funzione sovraccaricati su shared_ptr<T> non vedranno l'ereditarietà. La classe derivata sembrerà improvvisamente straniera a funzioni casuali come std::static_pointer_cast.

Fortunatamente, la libreria standard C++ è piena di ganci di estensibilità ordinata.È possibile installare un deleter personalizzato in questo modo:

template< typename t > 
struct my_deleter 
    : std::default_delete<t> { 
    std::string info; 

    my_deleter(std::string in_info) 
     : info(std::move(in_info)) {} 
}; 

std::shared_pointer<foo> myfoo(new foo, my_deleter{ "it's a foo" }); 

e recuperare le informazioni con una funzione non membro:

template< typename t > 
std::string get_my_info(std::shared_ptr<t> ptr) { 
    my_deleter<t> * dp = std::get_deleter< my_deleter<t> >(ptr); 
    if (! dp) return {}; 
    return dp->info; 
} 

Questa non è una buona architettura di programma, dal momento che c'è solo deleter uno personalizzato slot per oggetto condiviso. Può fare in un pizzico, però.

Problemi correlati