Warum wird unique_ptr <Derived> implizit in unique_ptr <Base> umgewandelt?

21

Ich habe den folgenden Code geschrieben, der verwendet, unique_ptr<Derived>wo a unique_ptr<Base>erwartet wird

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

und ich erwartete diesen Code nicht kompilieren, weil nach meinem Verständnis unique_ptr<Base>und unique_ptr<Derived>nicht verwandte Arten sind und unique_ptr<Derived>ist nicht in der Tat , die von unique_ptr<Base>so die Zuordnung nicht funktionieren soll.

Aber dank etwas Magie funktioniert es und ich verstehe nicht warum oder auch wenn es sicher ist. Kann mir bitte jemand erklären?

Youda008
quelle
3
Intelligente Zeiger sollen bereichern, was Zeiger tun können, um sie nicht einzuschränken. Wenn dies nicht möglich unique_ptrwäre, wäre es in Gegenwart von Vererbung eher nutzlos
idclev 463035818
3
"Aber dank etwas Magie funktioniert es" . Fast haben Sie UB, da Basees keinen virtuellen Destruktor gibt.
Jarod42

Antworten:

25

Das bisschen Magie, das Sie suchen, ist der Konvertierungskonstruktor Nr. 6 hier :

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

Es ermöglicht das std::unique_ptr<T>implizite Konstruieren eines aus einem ablaufenden std::unique_ptr<U> if (Beschönigen von Deletern zur Klarheit):

unique_ptr<U, E>::pointer ist implizit konvertierbar zu pointer

Das heißt, es ahmt implizite Rohzeigerkonvertierungen nach, einschließlich Konvertierungen von abgeleiteten zu Basen, und führt das, was Sie erwarten, sicher aus (in Bezug auf die Lebensdauer - Sie müssen weiterhin sicherstellen, dass der Basistyp polymorph gelöscht werden kann).

QUentin
quelle
2
AFAIK, der Deleter von, Basewird den Destruktor von nicht anrufen Derived, daher bin ich mir nicht sicher, ob es wirklich sicher ist. (Es ist zugegebenermaßen nicht weniger sicher als roher Zeiger.)
cpplearner
14

Da std::unique_ptrhat ein konvertierender Konstruktor als

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

und

Dieser Konstruktor nimmt nur dann an der Überlastungsauflösung teil, wenn alle der folgenden Bedingungen erfüllt sind:

a) unique_ptr<U, E>::pointerist implizit konvertierbar inpointer

...

A Derived*könnte Base*implizit in konvertieren , dann könnte der konvertierende Konstruktor für diesen Fall angewendet werden. Dann std::unique_ptr<Base>könnte a std::unique_ptr<Derived>implizit von einem konvertiert werden, genau wie der Rohzeiger. (Beachten Sie, dass das aufgrund der Eigenschaft von std::unique_ptr<Derived>ein r-Wert für die Konstruktion sein muss .)std::unique_ptr<Base>std::unique_ptr

songyuanyao
quelle
7

Sie können eine Instanz implizitstd::unique_ptr<T> aus einem Wert von std::unique_ptr<S>wann Simmer konvertierbar ist erstellenT . Dies liegt an Konstruktor Nr. 6 hier . In diesem Fall geht das Eigentum über.

In Ihrem Beispiel haben Sie nur r-Werte vom Typ std::uinque_ptr<Derived>(da der Rückgabewert von std::make_uniqueein r- Wert ist), und wenn Sie diesen als verwenden std::unique_ptr<Base>, wird der oben erwähnte Konstruktor aufgerufen. Die std::unique_ptr<Derived>fraglichen Objekte leben daher nur für eine kurze Zeit, dh sie werden erstellt, und dann wird das Eigentum an das std::unique_ptr<Base>Objekt weitergegeben, das weiter verwendet wird.

lubgr
quelle