Converting unique_ptr to shared_ptr: Factory function example

Question | Nov 25, 2019 | nextptr 

Background

The std::unique_ptr and std::shared_ptr are smart-pointers. An std::unique_ptr owns an object exclusively, whereas the ownership of an object can be shared via std::shared_ptr instances. One of the helpful features of the std::unique_ptr is that it can be seamlessly converted to a compatible std::shared_ptr. The conversion is possible through a constructor in std::shared_ptr that takes an rvalue reference of an std::unique_ptr. Here are some trivial examples:

std::unique_ptr<std::string> foo();

//from function result
std::shared_ptr<std::string> sptr1 = foo(); // OK

std::unique_ptr<std::string> uptr;
// By moving an lvalue
std::shared_ptr<std::string> sptr2 = std::move(uptr); // OK

After the conversion, the std::unique_ptr is moved and does not own the object. One practical use of this feature is in the factory function pattern, which often produces an instance and returns a pointer to it. A factory method implementation cannot assume whether the ownership of the returned object would be shared, or it would be owned exclusively. One way is to return a raw pointer and let the callers of the factory function wrap the raw pointer in an std::shared_ptr or an std::unique_ptr. But the drawback of that approach is that we have to renounce the std::make_unique (or std::make_shared). Moreover, the use of a raw pointer inside a factory method is unsafe.

A more flexible design is to return an std::unique_ptr from a factory method, which can be used as-is or can be converted to a compatible std::shared_ptr by the callers.

An Example and The Question

Consider a polymorphic class hierarchy of shapes, as shown below:

struct Base { 
 // ...
 virtual ~Base() { }
};
struct Derived1 : public Base {
 // ...
 ~Derived1() { }
};
struct Derived2 : public Base {
 // ...
 virtual ~Derived2() { }
};

A factory function creates a shape depending on the parameters and returns an std::unique_ptr<Base>:

std::unique_ptr<Base> factory(int t) {
 switch(t) {
   case 1:
     return std::make_unique<Derived1>();
   case 2:
     return std::make_unique<Derived2>();
   default:
     return nullptr;
 }
}

Below are a few cases of converting the result of the factory function to an std::shared_ptr:

A

std::shared_ptr<Base> ptrBase = factory(1);

B

std::shared_ptr<Derived2> ptrDerived2 = factory(2);

C

std::shared_ptr<void> ptrVoid = factory(1);

One or more of the above cases are invalid (would not compile) because of the use of incompatible std::shared_ptr.

Select below all the valid cases from above (check Explanations for details):


Afterword

The flawless conversion of an std::unique_ptr to a compatible std::shared_ptr makes it possible to write efficient and safe factory functions. However, note that an std::shared_ptr cannot be converted to an std::unique_ptr.

References

Constructors of std::shared_ptr - cppreference

The std::shared_ptr as arbitrary user-data pointer - nextptr

2015 nextptr