Почему unique_ptr<derived> неявно приводится к unique_ptr<base>?
Я написал следующий код, который использует 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>
, поэтому назначение не должно работать.
Но благодаря некоторой магии это работает, и я не понимаю, почему, и даже если это безопасно.
Может кто-нибудь объяснить, пожалуйста?
Ответы
Ответ 1
Волшебство, которое вы ищете, - это конструктор преобразования # 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
Иными словами, он имитирует неявные необработанные преобразования указателей, включая преобразования из производных в базовые, и выполняет то, что вы ожидаете ™ безопасно (с точки зрения срока службы - вам все еще нужно убедиться, что базовый тип может быть удален полиморфно).
Ответ 2
Потому что std::unique_ptr
имеет конструктор преобразования
template< class U, class E >
unique_ptr( unique_ptr<U, E>&& u ) noexcept;
и
Этот конструктор участвует только в разрешении перегрузки, если все верно следующее:
a) unique_ptr<U, E>::pointer
is implicitly convertible to pointer
...
Derived*
может неявно конвертировать в Base*
, тогда конструктор преобразования может быть применен для этого случая. Затем std::unique_ptr<Base>
может быть неявно преобразован из std::unique_ptr<Derived>
, как это делает необработанный указатель. (Обратите внимание, что std::unique_ptr<Derived>
должен быть основным значением для построения std::unique_ptr<Base>
из-за характеристики std::unique_ptr
.)
Ответ 3
Вы можете неявно создать экземпляр std::unique_ptr<T>
из значения std::unique_ptr<S>
всякий раз, когда S
конвертируется в T
. Это из-за конструктора # 6 здесь. В этом случае право собственности передается.
В вашем примере у вас есть только rvalues типа std::uinque_ptr<Derived>
(потому что возвращаемое значение std::make_unique
является rvalue), и когда вы используете его как std::unique_ptr<Base>
, вызывается упомянутый выше конструктор. Следовательно, рассматриваемые объекты std::unique_ptr<Derived>
живут только в течение короткого промежутка времени, то есть они создаются, затем право собственности передается объекту std::unique_ptr<Base>
, который используется в дальнейшем.