std::unique_ptr
embodies exclusive ownership semantics. A non-null std::unique_ptr
always owns what it points to. Moving a std::unique_ptr
transfers ownership from the source pointer to the destination pointer. (The source pointer is set to null.) Copying a std::unique_ptr
isn't allowed, because if you could copy a std::unique_ptr
, you'd end up with two std::unique_ptr
s to the same resource, each thinking it owned (and should therefore destroy) that resource. std::unique_ptr
is thus a move-only type . Upon destruction, a non-null std::unique_ptr
destroys its resource. By default, resource destruction is accomplished by applying delete
to the raw pointer inside the std::unique_ptr
.
A common use for std::unique_ptr
is as a factory function return type for objects in a hierarchy. Suppose we have a hierarchy for types of investments (e.g., stocks, bonds, real estate, etc.) with a base class Investment
.
class Investment { … };
class Stock:
public Investment { …
};
class Bond:
public Investment { … };
class RealEstate:
public Investment { …
};
A factory function for such a hierarchy typically allocates an object on the heap and returns a pointer to it, with the caller being responsible for deleting the object when it's no longer needed. That's a perfect match for std::unique_ptr
, because the caller acquires responsibility for the resource returned by the factory (i.e., exclusive ownership of it), and the std::unique_ptr
automatically deletes what it points to when the std::unique_ptr
is destroyed. A factory function for the Investment
hierarchy could be declared like this:
template // return std::unique_ptr
std::unique_ptr // to an object created
makeInvestment(Ts&&... params); // from the given args
Callers could use the returned std::unique_ptr
in a single scope as follows,
{
…
auto pInvestment = // pInvestment is of type
makeInvestment( arguments ); // std::unique_ptr
…
} // destroy *pInvestment
but they could also use it in ownership-migration scenarios, such as when the std::unique_ptr
returned from the factory is moved into a container, the container element is subsequently moved into a data member of an object, and that object is later destroyed. When that happens, the object's std::unique_ptr
data member would also be destroyed, and its destruction would cause the resource returned from the factory to be destroyed. If the ownership chain got interrupted due to an exception or other atypical control flow (e.g., early function return or break
from a loop), the std::unique_ptr
owning the managed resource would eventually have its destructor called, [7] There are a few exceptions to this rule. Most stem from abnormal program termination. If an exception propagates out of a thread's primary function (e.g., main , for the program's initial thread) or if a noexcept specification is violated (see Item 14 ), local objects may not be destroyed, and if std::abort or an exit function (i.e., std::_Exit , std::exit , or std::quick_exit ) is called, they definitely won't be.
and the resource it was managing would thereby be destroyed.
By default, that destruction would take place via delete
, but, during construction, std::unique_ptr
objects can be configured to use custom deleters : arbitrary functions (or function objects, including those arising from lambda expressions) to be invoked when it's time for their resources to be destroyed. If the object created by makeInvestment
shouldn't be directly delete
d, but instead should first have a log entry written, makeInvestment
could be implemented as follows. (An explanation follows the code, so don't worry if you see something whose motivation is less than obvious.)
auto delInvmt = [](Investment* pInvestment) // custom
{ // deleter
makeLogEntry(pInvestment); // (a lambda
delete pInvestment; // expression)
};
template // revised
std::unique_ptr // return type
makeInvestment(Ts&&... params) {
std::unique_ptr // ptr to be
pInv(nullptr, delInvmt); // returned
if ( /* a Stock object should be created */ ) {
pInv.reset(new Stock(std::forward(params)...));
}
else if ( /* a Bond object should be created */ ) {
pInv.reset(new Bond(std::forward(params)...));
}
else if ( /* a RealEstate object should be created */ ){
pInv.reset(new RealEstate(std::forward(params)...));
}
return pInv;
}
In a moment, I'll explain how this works, but first consider how things look if you're a caller. Assuming you store the result of the makeInvestment
call in an auto
variable, you frolic in blissful ignorance of the fact that the resource you're using requires special treatment during deletion. In fact, you veritably bathe in bliss, because the use of std::unique_ptr
means you need not concern yourself with when the resource should be destroyed, much less ensure that the destruction happens exactly once along every path through the program. std::unique_ptr
takes care of all those things automatically. From a client's perspective, makeInvestment
's interface is sweet.
The implementation is pretty nice, too, once you understand the following:
• delInvmt
is the custom deleter for the object returned from makeInvestment
. All custom deletion functions accept a raw pointer to the object to be destroyed, then do what is necessary to destroy that object. In this case, the action is to call makeLogEntry
and then apply delete
. Using a lambda expression to create delInvmt
is convenient, but, as we'll see shortly, it's also more efficient than writing a conventional function.
• When a custom deleter is to be used, its type must be specified as the second type argument to std::unique_ptr
. In this case, that's the type of delInvmt
, and that's why the return type of makeInvestment
is std::unique_ptr
. (For information about decltype
, see Item 3.)
• The basic strategy of makeInvestment
is to create a null std::unique_ptr
, make it point to an object of the appropriate type, and then return it. To associate the custom deleter delInvmt
with pInv
, we pass that as its second constructor argument.
• Attempting to assign a raw pointer (e.g., from new
) to a std::unique_ptr
won't compile, because it would constitute an implicit conversion from a raw to a smart pointer. Such implicit conversions can be problematic, so C++11's smart pointers prohibit them. That's why reset
is used to have pInv
assume ownership of the object created via new
.
Читать дальше