std::unique_ptrembodies exclusive ownership semantics. A non-null std::unique_ptralways owns what it points to. Moving a std::unique_ptrtransfers ownership from the source pointer to the destination pointer. (The source pointer is set to null.) Copying a std::unique_ptrisn't allowed, because if you could copy a std::unique_ptr, you'd end up with two std::unique_ptrs to the same resource, each thinking it owned (and should therefore destroy) that resource. std::unique_ptris thus a move-only type . Upon destruction, a non-null std::unique_ptrdestroys its resource. By default, resource destruction is accomplished by applying deleteto the raw pointer inside the std::unique_ptr.
A common use for std::unique_ptris 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_ptrautomatically deletes what it points to when the std::unique_ptris destroyed. A factory function for the Investmenthierarchy 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_ptrin 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_ptrreturned 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_ptrdata 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 breakfrom a loop), the std::unique_ptrowning 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_ptrobjects 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 makeInvestmentshouldn't be directly deleted, but instead should first have a log entry written, makeInvestmentcould 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 makeInvestmentcall in an autovariable, 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_ptrmeans 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_ptrtakes 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:
• delInvmtis 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 makeLogEntryand then apply delete. Using a lambda expression to create delInvmtis 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 makeInvestmentis std::unique_ptr. (For information about decltype, see Item 3.)
• The basic strategy of makeInvestmentis 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 delInvmtwith pInv, we pass that as its second constructor argument.
• Attempting to assign a raw pointer (e.g., from new) to a std::unique_ptrwon'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 resetis used to have pInvassume ownership of the object created via new.
Читать дальше