Reputation: 16195
Given the common situation where the lifespan of an owned object is linked to its owner, I can use a unique pointer one of 2 ways . .
It can be assigned:
class owner
{
std::unique_ptr<someObject> owned;
public:
owner()
{
owned=std::unique_ptr<someObject>(new someObject());
}
};
The reset method can be utilised:
class owner
{
std::unique_ptr<someObject> owned;
public:
owner()
{
owned.reset(new someObject());
}
};
In the interests of best practice, should I prefer one form over the other?
EDIT: Sorry folks. I over simplified this. The heap allocation occurs in an initialise method and not in the ctor. Therefore, I cannot use initialiser lists.
Upvotes: 90
Views: 92196
Reputation: 8359
Short Answer: Both solutions are equivalent. Which to choose is a matter of style or what you want to express.
In Detail: The best and most preferable solution is to use a different design: try to avoid any lifecycle state. This can be achieved by initialising the unique_ptr
immediately with the newly heap allocated instance:
class owner
{
std::unique_ptr<someObject> owned;
public:
owner()
: owned{new someObject()}
{ }
};
Why is this preferable? because it either succeeds or fails as a whole. If the allocation fails or if the ctor of someObject
blows up, the construction of owner
also throws. This implies: you can get an owner
instance only in complete, valid state. As an added bonus, there is no intermediary involved.
However — there are cases when you can not create at initialisation. This is especially the case when your use case explicitly calls for lifecycle state: There may be a situation where you need the owned
to be empty sometimes, and only place the someObject
later into it, triggered by external events.
In such a situation the question arises if we should assign from make_unique
or if we should reset()
. Both are equivalent in terms of functionality. Both will safely clean-up another someObject
instance which happens to sit in the unique_ptr
. Both will transfer ownership and move the deleter alongside.
reset()
implies the meaning that you change or re-set something that is already there. It may be misleading in cases where the unique_ptr
was previously empty. Moreover reset()
solution has the benefit of being shorter, more explicit (you can see the "new")make_unique
conveys the meaning on a more abstract level: "make an unique someObject
instance and place it here". Some people find that clearer and more precise, other people think it hides the actual allocation. Moreover, make_unique
creates an temporary, which is moved into the target. The optimiser is allowed to elide that altogether.Generally speaking, make_unique
can change the exception safety in some situations. For this reason, it is often recommended to make a habit of using make_unique
. In practice, this becomes relevant if you create several unique-managed objects.
someFunction(make_unique<someObject>(arg1),
make_unique<someObject>(arg2));
If the ctor of one fails, the other one is always safely managed.
However, in the situation discussed here, we only create one object. So what happens in case of an exeption?
reset(new someObject)
, the argument is created first. If this blows up, fine, the reset function is not called. BUT if reset()
has to clean-up an existing other instance, and the destructor of that one throws, then we've got a problem: the reset()
call will be left by exception, and the newly heap allocated argument leaks.make_unique()
, this special situation is handled safely: if the destructor of the old instance throws, stack unwinding will call the destructor of the temporary returned by make_unique()
.BUT this is a mostly theoretical argument. In practice, there is kind of a »social contract« to the end that decent people do not throw on destruction. And this stands for good reason: objects throwing on dtor call can cause endless pain and basically jeopardise the ability to handle any kind of "situation".
Upvotes: 0
Reputation: 72241
From the docs of unique_ptr
's operator=
:
Transfers ownership of the object pointed to by r to *this as if by calling
reset(r.release())
followed by an assignment fromstd::forward<E>(r.get_deleter())
.
And all you need of that is the reset
call, so it's simpler to just call it directly
Upvotes: 59
Reputation: 32202
The proper way to do this (that you didn't list) is to use the constructor of owned
:
owner() : owned(new someObject())
{}
Apart from that I'd prefer reset
as you don't create a useless intermediate instance in that case (even though there might be no difference on the machine level as the optimizer can do a lot there).
Upvotes: 19