Perché unique_ptr <Derived> esegue il cast implicito su unique_ptr <Base>?


21

Ho scritto il seguente codice che utilizza unique_ptr<Derived>dove unique_ptr<Base>è previsto un

class Base {
    int i;
 public:
    Base( int i ) : i(i) {}
    int getI() const { return i; }
};

class Derived : public Base {
    float f;
 public:
    Derived( int i, float f ) : Base(i), f(f) {}
    float getF() const { return f; }
};

void printBase( unique_ptr<Base> base )
{
    cout << "f: " << base->getI() << endl;
}

unique_ptr<Base> makeBase()
{
    return make_unique<Derived>( 2, 3.0f );
}

unique_ptr<Derived> makeDerived()
{
    return make_unique<Derived>( 2, 3.0f );
}

int main( int argc, char * argv [] )
{
    unique_ptr<Base> base1 = makeBase();
    unique_ptr<Base> base2 = makeDerived();
    printBase( make_unique<Derived>( 2, 3.0f ) );

    return 0;
}

e mi aspettavo che questo codice non venisse compilato, perché secondo la mia comprensione unique_ptr<Base>e unique_ptr<Derived>sono tipi non correlati e unique_ptr<Derived>non è in realtà derivato da unique_ptr<Base>così l'assegnazione non dovrebbe funzionare.

Ma grazie ad un po 'di magia funziona, e non capisco perché, o anche se è sicuro farlo. Qualcuno può spiegare per favore?


3
i puntatori intelligenti servono ad arricchire ciò che i puntatori possono fare per non limitarlo. Se ciò non fosse possibile unique_ptrsarebbe piuttosto inutile in presenza dell'eredità
idclev 463035818

3
"Ma grazie ad un po 'di magia funziona" . Quasi, hai UB in quanto Basenon ha distruttore virtuale.
Jarod42

Risposte:


25

Il pizzico di magia che stai cercando è il costruttore di conversione n. 6 qui :

template<class U, class E>
unique_ptr(unique_ptr<U, E> &&u) noexcept;

Permette di costruire std::unique_ptr<T>implicitamente un in scadenza std::unique_ptr<U> se (passando per deleter per chiarezza):

unique_ptr<U, E>::pointer è implicitamente convertibile in pointer

Vale a dire, imita le conversioni implicite di puntatori non elaborati, comprese le conversioni derivate-base, e fa quello che ti aspetti ™ in modo sicuro (in termini di durata della vita, devi comunque assicurarti che il tipo di base possa essere eliminato polimorficamente).


2
AFAIK il deleter di Basenon chiamerà il distruttore di Derived, quindi non sono sicuro che sia davvero sicuro. (Certo, non è meno sicuro del puntatore non
elaborato

14

Perché std::unique_ptrha un costruttore di conversione come

template< class U, class E >
unique_ptr( unique_ptr<U, E>&& u ) noexcept;

e

Questo costruttore partecipa alla risoluzione di sovraccarico solo se è vero quanto segue:

a) unique_ptr<U, E>::pointerè implicitamente convertibile inpointer

...

A Derived*potrebbe convertirsi in modo Base*implicito, quindi il costruttore di conversione potrebbe essere applicato per questo caso. Quindi a std::unique_ptr<Base>potrebbe essere convertito da un std::unique_ptr<Derived>implicitamente proprio come fa il puntatore raw. (Si noti che std::unique_ptr<Derived>deve essere un valore per la costruzione a std::unique_ptr<Base>causa della caratteristica di std::unique_ptr.)


7

È possibile costruire implicitamentestd::unique_ptr<T> un'istanza da un valore di std::unique_ptr<S>ogni volta che Sè convertibile T. Ciò è dovuto al costruttore n. 6 qui . La proprietà viene trasferita in questo caso.

Nel tuo esempio, hai solo valori di tipo std::uinque_ptr<Derived>(perché il valore restituito di std::make_uniqueè un valore) e quando lo usi come a std::unique_ptr<Base>, viene invocato il costruttore sopra menzionato. Gli std::unique_ptr<Derived>oggetti in questione quindi vivono solo per un breve periodo di tempo, cioè vengono creati, quindi la proprietà viene passata std::unique_ptr<Base>all'oggetto che viene usato più avanti.

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.