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>::pointeris 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