Mengapa unique_ptr <Derived> secara implisit dilemparkan ke unique_ptr <Base>?

21

Saya menulis kode berikut yang menggunakan unique_ptr<Derived>tempat yang unique_ptr<Base>diharapkan

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

dan saya berharap kode ini tidak dapat dikompilasi, karena menurut pemahaman saya unique_ptr<Base>dan unique_ptr<Derived>tidak terkait jenis dan unique_ptr<Derived>sebenarnya bukan berasal dari unique_ptr<Base>sehingga tugas tidak seharusnya bekerja.

Tetapi berkat beberapa sihir itu berhasil, dan saya tidak mengerti mengapa, atau bahkan jika itu aman untuk melakukannya. Bisakah seseorang menjelaskannya?

Youda008
sumber
3
pointer pintar adalah untuk memperkaya pointer apa yang bisa dilakukan untuk tidak membatasi itu. Jika ini tidak mungkin unique_ptrakan menjadi agak tidak berguna di hadapan warisan
idclev 463035818
3
"Tapi berkat beberapa sihir itu berhasil" . Hampir, Anda mendapatkan UB karena Basetidak memiliki destruktor virtual.
Jarod42

Jawaban:

25

Sedikit keajaiban yang Anda cari adalah konstruktor konversi # 6 di sini :

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

Ini memungkinkan untuk membangun secara std::unique_ptr<T>implisit dari std::unique_ptr<U> if yang kedaluwarsa (mengabaikan batasan untuk kejelasan):

unique_ptr<U, E>::pointer secara implisit dapat dikonversi ke pointer

Dengan kata lain, ini meniru konversi penunjuk mentah tersirat, termasuk konversi turunan ke basis, dan melakukan apa yang Anda harapkan ™ dengan aman (dalam hal masa pakai - Anda masih perlu memastikan bahwa tipe dasar dapat dihapus secara polimorfik).

Quentin
sumber
2
AFAIK deleter of Basetidak akan memanggil destruktor Derived, jadi saya tidak yakin apakah itu benar-benar aman. (Memang tidak kalah aman dari pointer mentah, diakui.)
cpplearner
14

Karena std::unique_ptrmemiliki konstruktor konversi sebagai

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

dan

Konstruktor ini hanya berpartisipasi dalam resolusi kelebihan jika semua hal berikut ini benar:

a) unique_ptr<U, E>::pointersecara implisit dapat dikonversi kepointer

...

A Derived*dapat mengkonversi Base*secara implisit, maka konstruktor yang dikonversi dapat diterapkan untuk kasus ini. Kemudian a std::unique_ptr<Base>dapat dikonversi dari std::unique_ptr<Derived>implisit seperti halnya pointer mentah. (Perhatikan bahwa std::unique_ptr<Derived>harus menjadi nilai untuk membangun std::unique_ptr<Base>karena karakteristik std::unique_ptr.)

songyuanyao
sumber
7

Anda dapat secara implisit membangun std::unique_ptr<T>contoh dari nilai p dari std::unique_ptr<S>setiap kali Sbisa dikonversi ke T. Ini karena konstruktor # 6 di sini . Kepemilikan ditransfer dalam kasus ini.

Dalam contoh Anda, Anda hanya memiliki nilai jenis std::uinque_ptr<Derived>(karena nilai pengembalian std::make_uniqueadalah nilai), dan ketika Anda menggunakannya sebagai std::unique_ptr<Base>, konstruktor yang disebutkan di atas dipanggil. The std::unique_ptr<Derived>benda tersebut maka hanya hidup untuk waktu singkat, yaitu mereka diciptakan, maka kepemilikan diteruskan ke std::unique_ptr<Base>objek yang digunakan lebih lanjut.

lubgr
sumber