Pourquoi unique_ptr <Derived> est-il implicitement converti en unique_ptr <Base>?

21

J'ai écrit le code suivant qui utilise unique_ptr<Derived>où un unique_ptr<Base>est attendu

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;
}

et je m'attendais à ce que ce code ne se compile pas, car selon ma compréhension, unique_ptr<Base>il unique_ptr<Derived>s'agit de types non liés et unique_ptr<Derived>ne sont en fait pas dérivés de unique_ptr<Base>sorte que l'affectation ne devrait pas fonctionner.

Mais grâce à la magie, cela fonctionne, et je ne comprends pas pourquoi, ou même si c'est sûr de le faire. Quelqu'un peut-il expliquer s'il vous plaît?

Youda008
la source
3
les pointeurs intelligents doivent enrichir ce que les pointeurs peuvent ne pas limiter. Si cela n'était pas possible, ce unique_ptrserait plutôt inutile en présence de l'héritage
idclev 463035818
3
"Mais grâce à un peu de magie ça marche" . Presque, vous avez obtenu UB car Basen'a pas de destructeur virtuel.
Jarod42

Réponses:

25

Le peu de magie que vous recherchez est le constructeur de conversion # 6 ici :

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

Il permet de construire std::unique_ptr<T>implicitement à partir d'un std::unique_ptr<U> if expirant (en glissant sur les délétères pour plus de clarté):

unique_ptr<U, E>::pointer est implicitement convertible en pointer

Autrement dit, il imite les conversions implicites de pointeurs bruts, y compris les conversions dérivées en base, et fait ce que vous attendez ™ en toute sécurité (en termes de durée de vie - vous devez toujours vous assurer que le type de base peut être supprimé par polymorphisme).

Quentin
la source
2
AFAIK le délétère de Basen'appellera pas le destructeur de Derived, donc je ne sais pas si c'est vraiment sûr. (Il n'est pas moins sûr que le pointeur brut, certes.)
cpplearner
14

Parce que std::unique_ptrpossède un constructeur de conversion

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

et

Ce constructeur ne participe à la résolution de surcharge que si toutes les conditions suivantes sont remplies:

a) unique_ptr<U, E>::pointerest implicitement convertible enpointer

...

A Derived*pourrait être converti Base*implicitement, puis le constructeur de conversion pourrait être appliqué dans ce cas. Ensuite, un std::unique_ptr<Base>pourrait être converti à partir d'un std::unique_ptr<Derived>implicitement comme le fait le pointeur brut. (Notez que le std::unique_ptr<Derived>doit être une valeur r pour la construction en std::unique_ptr<Base>raison de la caractéristique de std::unique_ptr.)

songyuanyao
la source
7

Vous pouvez implicitement construire un std::unique_ptr<T>exemple d'un rvalue de std::unique_ptr<S>chaque fois Sest convertible T. Cela est dû au constructeur # 6 ici . La propriété est transférée dans ce cas.

Dans votre exemple, vous ne disposez que de rvalues ​​de type std::uinque_ptr<Derived>(car la valeur de retour de std::make_uniqueest une rvalue), et lorsque vous l'utilisez comme a std::unique_ptr<Base>, le constructeur mentionné ci-dessus est appelé. Les std::unique_ptr<Derived>objets en question ne vivent donc que pendant une courte période, c'est-à-dire qu'ils sont créés, puis la propriété est transférée à l' std::unique_ptr<Base>objet qui est utilisé plus loin.

lubgr
la source