I wrote the following code that uses unique_ptr<Derived>
where a unique_ptr<Base>
is expected
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; }
and i expected this code to not compile, because according to my understanding unique_ptr<Base>
and unique_ptr<Derived>
are unrelated types and unique_ptr<Derived>
isn't in fact derived from unique_ptr<Base>
so the assignment shouldn't work.
But thanks to some magic it works, and i don't understand why, or even if it's safe to do so. Can someone explain please?
A unique_ptr can only be moved. This means that the ownership of the memory resource is transferred to another unique_ptr and the original unique_ptr no longer owns it. We recommend that you restrict an object to one owner, because multiple ownership adds complexity to the program logic.
std::unique_ptr represents data which has only one owner at any given time. It should be your default choice when you need a smart pointer. You can move a std::unique_ptr around to keep it alive, but there can only be one owner of the data. After moving the pointer, the previous pointer object is invalidated.
An unique_ptr has exclusive ownership of the object it points to and will destroy the object when the pointer goes out of scope. A unique_ptr explicitly prevents copying of its contained pointer.
That is, you should know that a unique_ptr will safely delete its underlying raw pointer once it goes out of scope.
The bit of magic you're looking for is the converting constructor #6 here:
template<class U, class E> unique_ptr(unique_ptr<U, E> &&u) noexcept;
It enables constructing a std::unique_ptr<T>
implicitly from an expiring std::unique_ptr<U>
if (glossing over deleters for clarity):
unique_ptr<U, E>::pointer
is implicitly convertible topointer
Which is to say, it mimicks implicit raw pointer conversions, including derived-to-base conversions, and does what you expect™ safely (in terms of lifetime – you still need to ensure that the base type can be deleted polymorphically).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With