Чому unique_ptr <Derived> неявно переходить на unique_ptr <Base>?


21

Я написав наступний код, який використовує, unique_ptr<Derived>де unique_ptr<Base>очікується

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

і я очікував цей код не компілюється, тому що , згідно з моїм відчуттям , unique_ptr<Base>і НЕ unique_ptr<Derived>мають ніякого відношення типу і unique_ptr<Derived>в дійсності не є похідною від unique_ptr<Base>так що присвоювання не повинно працювати.

Але завдяки якійсь магії це працює, і я не розумію, чому, і навіть якщо це безпечно зробити. Може хтось пояснить, будь ласка?


3
розумні покажчики - це збагатити те, що покажчики не можуть його обмежити. Якщо це було можливо, unique_ptrбуло б досить марно за наявності спадщини
idclev 463035818

3
"Але завдяки якійсь магії це працює" . Майже у вас є UB, оскільки Baseвін не має віртуального деструктора.
Jarod42

Відповіді:


25

Трохи магії, яку ви шукаєте, - це конструктор, що перетворює номер 6 тут :

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

Це дозволяє побудувати std::unique_ptr<T>неявно з закінчення терміну, std::unique_ptr<U> якщо (промальовування делетерів для наочності):

unique_ptr<U, E>::pointer неявно перетворюється в pointer

Що означає, він імітує неявні перетворення вхідних покажчиків, включаючи перетворення на базі, і робить те, що ви очікуєте ™ безпечно (з точки зору життя - вам все одно потрібно гарантувати, що базовий тип можна буде видалити поліморфно).


2
AFAIK делетер Baseне викличе деструктора Derived, тому я не впевнений, чи справді це безпечно. (Це не менш безпечно, ніж сирий покажчик, правда
,.

14

Тому що std::unique_ptrмає перетворювач як

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

і

Цей конструктор бере участь у роздільній здатності перевантаження, якщо справедливо все наступне:

а) unique_ptr<U, E>::pointerнеявно перетворюється вpointer

...

A Derived*може перетворитись Base*неявно, тоді конструктор, що перетворює, може бути застосований у цьому випадку. Тоді файл std::unique_ptr<Base>може бути перетворений std::unique_ptr<Derived>неявно, як і необроблений покажчик. (Зауважте, що std::unique_ptr<Derived>має бути оцінкою для побудови std::unique_ptr<Base>через властивості std::unique_ptr.)


7

Ви можете неявно побудувати std::unique_ptr<T>екземпляр з RValue в std::unique_ptr<S>випадках , коли Sконвертується в T. Це пов’язано з конструктором №6 тут . Власність передається в цьому випадку.

У вашому прикладі у вас є лише типи rvalues std::uinque_ptr<Derived>(тому що повернене значення std::make_unique- це rvalue), і коли ви використовуєте це як a std::unique_ptr<Base>, викликається згаданий вище конструктор. Отже, std::unique_ptr<Derived>об'єкти, про які йдеться, живуть лише короткий проміжок часу, тобто вони створюються, після чого право власності передається std::unique_ptr<Base>об'єкту, який використовується далі.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.