This is a follow up to the question that was asked here: Does C++11 unique_ptr and shared_ptr able to convert to each other's type? and more specifically the top voted answer.
The snippet that caught my attention was this:
This is a key part of why std::unique_ptr is so well suited as a factory function return type. Factory functions can’t know whether callers will want to use exclusive ownership semantics for the object they return or whether shared ownership (i.e., std::shared_ptr) would be more appropriate. By returning a std::unique_ptr, factories provide callers with the most efficient smart pointer, but they don’t hinder callers from replacing it with its more flexible sibling.
And then I saw a duplicate question is assigning unique_ptr to shared_ptr allowed? that had a comment:
Why shouldn't be? It's even safer than assigning regular pointer...[more text here].
However the user did not elaborate why it was safer than assigning a regular pointer. I am wondering in what cases would it be best to assign a unique_ptr to a shared_ptr? If I understood correctly, the first quoted section, cases like this are the best: (Let's assume that we have a working base class Car
and all of the CarType
s inherit from Car
.)
enum class CarType
{
Sedan,
SUV,
Crossover,
Truck,
Bus
};
class MyRental
{
public:
MyRental();
void assignRentalCar(CarType type);
void doDriveAction();
void doTurnAction();
void doParkAction();
private:
std::shared_ptr<Car> currentCar;
std::shared_ptr<Bus> bus; // a Oversized Vehicle Bus : (public OversizedCar : public Car)
};
From above in our private members lets say we have a special case where the member variable, bus
, inherits from an OversizeCar
class which inherits from a Car
class.
MyRental::MyRental() : currentCar(std::make_unique<Sedan>()),
bus(std::make_unique<Bus>())
{}
void MyRental::assignRentalCar(CarType type)
{
switch(type)
{
case CarType::SUV: currentCar = std::make_unique<SUV>(); break;
case CarType::Crossover: currentCar = std::make_unique<Crossover>(); break;
case CarType::Truck: currentCar = std::make_unique<Truck>(); break;
case CarType::Bus: currentCar = bus; break; // allowed because currentCar is a shared_ptr
case CarType::Sedan:
case default: currentCar = std::make_unique<Sedan>(); break;
}
}
void MyRental::doDriveAction()
{
currentCar->handleDrive();
}
void MyRental::doTurnAction()
{
currentCar->handleTurn();
}
void MyRental::doParkAction()
{
currentCar->handlePark();
}
Is this an appropriate case to assign a unique_ptr
to a shared_ptr
? And if so why is it appropriate? If it is not appropriate and better to assign shared_ptr
s instead, when is it best to assign a unique_ptr
to a shared_ptr
?
I am wondering in what cases would it be best to assign a unique_ptr to a shared_ptr?
You're overthinking this, but I suppose it is a valid question. The scenarios where an assignment is reasonable are when the following hold.
u
, of type U
to assign from.s
, of type S
to assign to.s = u
is syntactically valid and useful for your code.S s(u)
is not a reasonable option. If you can reasonably use construction instead of assignment, do that.These criteria are good for many types, including when U
is std::unique_ptr&&
and S
is std::shared_ptr
. One caveat for the smart pointer case is that the assignment must be from an rvalue. That is, if the unique_ptr
is not being returned by a function, you probably have to move it, as in s = std::move(u)
. Note that this move assignment will result in u
being null; you will not have s
and u
point to the same object at the same time.
Basically, use assignment when it is natural, and avoid it when it is not.
You did ask about "best" rather than "good", though. That brings in another consideration, also not particular to smart pointers: avoid unnecessary conversions. Assigning a unique_ptr<Truck>
to a shared_ptr<Car>
involves a more complex conversion than converting a shared_ptr<Truck>
to a shared_ptr<Car>
. In your example, you could readily simplify by replacing std::make_unique
with std::make_shared
. Keep it simple, and don't push others to ask "does a unique_ptr
convert to a shared_ptr
?"
Basically, stick to shared_ptr
when that is an option.