Почему unique_ptr <Derived>неявно приводится к unique_ptr <Base>? - PullRequest
21 голосов
/ 01 октября 2019

Я написал следующий код, который использует 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 ]

25 голосов
/ 01 октября 2019

Немного магии, которую вы ищете, это конвертирующий конструктор # 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

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

13 голосов
/ 01 октября 2019

Поскольку std::unique_ptr имеет конструктор преобразования в виде

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

и

Этот конструктор участвует только в разрешении перегрузкиесли все следующее верно:

a) 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 голосов
/ 01 октября 2019

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

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

...