You may be wondering how a std::weak_ptrcould be useful. You'll probably wonder even more when you examine the std::weak_ptrAPI. It looks anything but smart. std::weak_ptrs can't be dereferenced, nor can they be tested for nullness. That's because std::weak_ptrisn't a standalone smart pointer. It's an augmentation of std::shared_ptr.
The relationship begins at birth. std::weak_ptrs are typically created from std::shared_ptrs. They point to the same place as the std::shared_ptrs initializing them, but they don't affect the reference count of the object they point to:
auto spw = // after spw is constructed,
std::make_shared(); // the pointed-to Widget's
// ref count (RC) is 1. (See
// Item 21 for info on
// std::make_shared.)
…
std::weak_ptrwpw(spw); // wpw points to same Widget
// as spw. RC remains 1
…
spw = nullptr; // RC goes to 0, and the
// Widget is destroyed.
// wpw now dangles
std::weak_ptrs that dangle are said to have expired . You can test for this directly,
if ( wpw.expired()) … // if wpw doesn't point
// to an object.
but often what you desire is a check to see if a std::weak_ptrhas expired and, if it hasn't (i.e., if it's not dangling), to access the object it points to. This is easier desired than done. Because std::weak_ptrs lack dereferencing operations, there's no way to write the code. Even if there were, separating the check and the dereference would introduce a race condition: between the call to expiredand the dereferencing action, another thread might reassign or destroy the last std::shared_ptrpointing to the object, thus causing that object to be destroyed. In that case, your dereference would yield undefined behavior.
What you need is an atomic operation that checks to see if the std::weak_ptrhas expired and, if not, gives you access to the object it points to. This is done by creating a std::shared_ptrfrom the std::weak_ptr. The operation comes in two forms, depending on what you'd like to have happen if the std::weak_ptrhas expired when you try to create a std::shared_ptrfrom it. One form is std::weak_ptr::lock, which returns a std::shared_ptr. The std::shared_ptris null if the std::weak_ptrhas expired:
std::shared_ptr spw1 = wpw. lock(); // if wpw's expired,
// spw1 is null
auto spw2 = wpw. lock(); // same as above,
// but uses auto
The other form is the std::shared_ptrconstructor taking a std::weak_ptras an argument. In this case, if the std::weak_ptrhas expired, an exception is thrown:
std::shared_ptrspw3(wpw); // if wpw's expired,
// throw std::bad_weak_ptr
But you're probably still wondering about how std::weak_ptrs can be useful. Consider a factory function that produces smart pointers to read-only objects based on a unique ID. In accord with Item 18's advice regarding factory function return types, it returns a std::unique_ptr:
std::unique_ptr loadWidget(WidgetID id);
If loadWidgetis an expensive call (e.g., because it performs file or database I/O) and it's common for IDs to be used repeatedly, a reasonable optimization would be to write a function that does what loadWidgetdoes, but also caches its results. Clogging the cache with every Widgetthat has ever been requested can lead to performance problems of its own, however, so another reasonable optimization would be to destroy cached Widgets when they're no longer in use.
For this caching factory function, a std::unique_ptrreturn type is not a good fit. Callers should certainly receive smart pointers to cached objects, and callers should certainly determine the lifetime of those objects, but the cache needs a pointer to the objects, too. The cache's pointers need to be able to detect when they dangle, because when factory clients are finished using an object returned by the factory, that object will be destroyed, and the corresponding cache entry will dangle. The cached pointers should therefore be std::weak_ptrs — pointers that can detect when they dangle. That means that the factory's return type should be a std::shared_ptr, because s td::weak_ptrs can detect when they dangle only when an object's lifetime is managed by std::shared_ptrs.
Here's a quick-and-dirty implementation of a caching version of loadWidget:
std::shared_ptr fastLoadWidget(WidgetID id) {
static std::unordered_map
std::weak_ptr> cache;
auto objPtr = cache[id].lock(); // objPtr is std::shared_ptr
// to cached object (or null
// if object's not in cache)
if (!objPtr) { // if not in cache,
objPtr = loadWidget(id); // load it
cache[id] = objPtr; // cache it
}
return objPtr;
}
This implementation employs one of C++11's hash table containers ( std::unordered_map), though it doesn't show the WidgetIDhashing and equality-comparison functions that would also have to be present.
The implementation of fastLoadWidgetignores the fact that the cache may accumulate expired std::weak_ptrs corresponding to Widgets that are no longer in use (and have therefore been destroyed). The implementation can be refined, but rather than spend time on an issue that lends no additional insight into std::weak_ptrs, let's consider a second use case: the Observer design pattern. The primary components of this pattern are subjects (objects whose state may change) and observers (objects to be notified when state changes occur). In most implementations, each subject contains a data member holding pointers to its observers. That makes it easy for subjects to issue state change notifications. Subjects have no interest in controlling the lifetime of their observers (i.e., when they're destroyed), but they have a great interest in making sure that if an observer gets destroyed, subjects don't try to subsequently access it. A reasonable design is for each subject to hold a container of std::weak_ptrs to its observers, thus making it possible for the subject to determine whether a pointer dangles before using it.
As a final example of std::weak_ptr's utility, consider a data structure with objects A, B, and Cin it, where Aand Cshare ownership of Band therefore hold std::shared_ptrs to it:
Suppose it'd be useful to also have a pointer from Bback to A. What kind of pointer should this be?
There are three choices:
Читать дальше