2014-04-08 10 views
6

Nel recente overload journal sotto il tema Far rispettare la regola dello zero, gli autori descrivono come possiamo evitare di scrivere la Regola di cinque operatori, come le ragioni per loro scrittura sono:C++ Regola di Zero: la cancellazione polimorfico e il comportamento unique_ptr

  1. gestione delle risorse
  2. eliminazione polimorfico

Ed entrambi questi possono essere curati usando puntatori intelligenti.

Eccomi specificamente interessati nella seconda parte.

Si consideri il seguente frammento di codice:

class Base 
{ 
public: 
    virtual void Fun() = 0; 
}; 


class Derived : public Base 
{ 
public: 

    ~Derived() 
    { 
     cout << "Derived::~Derived\n"; 
    } 

    void Fun() 
    { 
     cout << "Derived::Fun\n"; 
    } 
}; 


int main() 
{ 
    shared_ptr<Base> pB = make_shared<Derived>(); 
    pB->Fun(); 
} 

In questo caso, come dell'articolo spiegano gli autori, si ottiene la cancellazione polimorfica utilizzando un puntatore condiviso, e questo funziona.

Ma se sostituisco lo shared_ptr con un unique_ptr, non sono più in grado di osservare l'eliminazione polimorfica.

Ora la mia domanda è: perché sono questi due comportamenti diversi? Perché shared_ptr prendersi cura di eliminazione polimorfico mentre unique_ptr non lo fa?

+0

come si sta inizializzando il 'unique_pointer'? –

+0

Ha importanza? In ogni caso: unique_ptr pB (new Derived()) – Arun

+6

Perché 'std :: shared_ptr' porta un puntatore alla funzione deleter. Quando assegni uno 'std :: shared_ptr' a uno compatibile il puntatore è uno dei membri copiati o spostati. Questo non succede con 'std :: unique_ptr' e dato che la tua classe base che non ha un distruttore virtuale ti viene disossata. –

risposta

3

avete la vostra risposta qui: https://stackoverflow.com/a/22861890/2007142

Citazione:

Una volta che l'ultimo riferimento shared_ptr va fuori del campo di applicazione o viene resettato, si chiamerà ~Derived() e la memoria rilasciato. Pertanto, non è necessario rendere ~Base() virtuale. unique_ptr<Base> e make_unique<Derived> non forniscono questa funzionalità, in quanto non forniscono la meccanica di shared_ptr rispetto al deleter, poiché il puntatore univoco è molto più semplice e punta all'overhead più basso e quindi non memorizza il puntatore di funzione aggiuntivo necessario per il deleter .

+0

ma puoi impostare un tipo di deleter diverso da quello predefinito, e sembra leggere come se fosse copiato in giro .. . – Yakk

1
template<typename T> 
using smart_unique_ptr=std::unique_ptr<T,void(*)(void*)>; 

template<class T, class...Args> smart_unique_ptr<T> make_smart_unique(Args&&...args) { 
    return {new T(std::forward<Args>(args)...), [](void*t){delete (T*)t;}}; 
} 

Il problema è che la delezione di default per le chiamate unique_ptrdelete sul puntatore memorizzato. I negozi di sopra di una deleter che conosce il tipo in costruzione, quindi se viene copiato in classe base unique_ptr sarà ancora eliminare come il bambino.

questo aggiunge modesta in testa, come abbiamo di dereference un puntatore. Inoltre denormalizza il tipo, dato che i default costruiti smart_unique_ptr s sono ora illegali. È possibile risolvere questo problema con un lavoro aggiuntivo (sostituire un puntatore a funzione non elaborata con un functor semi-intelligente che almeno non si blocca: il puntatore a funzione, tuttavia, deve essere affermato per esistere se unique non è vuoto quando viene richiamato il deleter) .

+0

Ho usato un'implementazione da questa risposta http://stackoverflow.com/a/13512344/602798 e ancora non mi dà la cancellazione polimorfa – Arun

+0

@Arun Dov'è il tuo dvd virtuale? –

+0

@Arun, perché non hai un lettore virtuale in Base? – ooga

2

funzionerà se si utilizza il C++ 14 make_unique o lascia la tua uno simile in risposta di Yakk. Fondamentalmente la differenza tra il comportamento del puntatore condiviso è che avete ottenuto:

template< 
    class T, 
    class Deleter = std::default_delete<T> 
> class unique_ptr; 

per unique_pointer e come si può vedere, la deleter appartiene al tipo. Se dichiari un unique_pointer<Base> userà sempre std::default_delete<Base> come predefinito. Ma make_unique si prenderà cura di usare il deleter corretto per la tua classe.

Quando si utilizza shared_ptr avete ottenuto:

template< class Y, class Deleter > 
shared_ptr(Y* ptr, Deleter d); 

e altri sovraccarichi come costruttore. Come si può vedere il deleter predefinito per unique_ptr dipende dal parametro del template quando si dichiara il tipo (a meno che non si usi make_unique) mentre per il shared_ptr il deleter dipende dal tipo passato al costruttore.


Si può vedere una versione che permette di eliminare polimorfico senza distruttore virtuale here (questa versione dovrebbe funzionare anche in VS2012). Si noti che è un po 'hackerato insieme e al momento non sono sicuro di quale sarà il comportamento di unique_ptr e make_shared in C++ 14, ma spero che lo renderanno più semplice. Forse esaminerò i documenti per le aggiunte al C++ 14 e vedrò se qualcosa è cambiato se avessi avuto il tempo dopo.

+0

Significa che se creo il parametro shared_ptr come this shared_ptr pB (new Derived()); Non otterrò l'eliminazione polimorfica? – Arun

+0

Ho provato a creare un shared_ptr come menzionato nel mio commento precedente. Ma ottengo ancora la cancellazione polimorfica (btw sto usando VS2012 spero che non sia un comportamento non standard specifico VS) – Arun