Почему 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>так что присваивание не должно работать.

Но благодаря некоторой магии это работает, и я не понимаю, почему, или даже если это безопасно. Может кто-нибудь объяснить, пожалуйста?

Youda008
источник
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> if (для ясности приглушая удалители):

unique_ptr<U, E>::pointer неявно конвертируется в pointer

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

Quentin
источник
2
AFAIK средство удаления Baseне вызовет деструктор Derived, поэтому я не уверен, действительно ли это безопасно. (Это не менее безопасно, чем необработанный указатель, по общему признанию.)
cpplearner
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*неявно, тогда конструктор преобразования может быть применен для этого случая. Тогда a std::unique_ptr<Base>может быть преобразовано из std::unique_ptr<Derived>неявно, как это делает необработанный указатель. (Обратите внимание, что std::unique_ptr<Derived>для построения необходимо использовать значение std::unique_ptr<Base>из-за характеристики std::unique_ptr.)

songyuanyao
источник
7

Вы можете неявно построить std::unique_ptr<T>экземпляр из RValue в std::unique_ptr<S>случаях , когда Sконвертируется в T. Это связано с конструктором № 6 здесь . Право собственности передается в этом случае.

В вашем примере у вас есть только rvalue типа std::uinque_ptr<Derived>(потому что возвращаемое значение std::make_uniqueявляется rvalue), и когда вы используете его как a std::unique_ptr<Base>, вызывается упомянутый выше конструктор. Следовательно, std::unique_ptr<Derived>рассматриваемые объекты живут только в течение короткого периода времени, то есть они создаются, а затем право собственности передается std::unique_ptr<Base>объекту, который используется в дальнейшем.

lubgr
источник