weak_ptr
A weak_ptr
(Table 12.5) is a smart pointer that does not control the lifetime of the object to which it points. Instead, a weak_ptr
points to an object that is managed by a shared_ptr
. Binding a weak_ptr
to a shared_ptr
does not change the reference count of that shared_ptr
. Once the last shared_ptr
pointing to the object goes away, the object itself will be deleted. That object will be deleted even if there are weak_ptr
s pointing to it—hence the name weak_ptr
, which captures the idea that a weak_ptr
shares its object “weakly.”
When we create a weak_ptr
, we initialize it from a shared_ptr
:
auto p = make_shared<int>(42);
weak_ptr<int> wp(p); // wp weakly shares with p; use count in p is unchanged
Here both wp
and p
point to the same object. Because the sharing is weak, creating wp
doesn’t change the reference count of p; it is possible that the object to which wp
points might be deleted.
Because the object might no longer exist, we cannot use a weak_ptr
to access its object directly. To access that object, we must call lock
. The lock
function checks whether the object to which the weak_ptr
points still exists. If so, lock
returns a shared_ptr
to the shared object. As with any other shared_ptr
, we are guaranteed that the underlying object to which that shared_ptr
points continues to exist at least as long as that shared_ptr
exists. For example:
if (shared_ptr<int> np = wp.lock()) { // true if np is not null
// inside the if, np shares its object with p
}
Here we enter the body of the if
only if the call to lock
succeeds. Inside the if
, it is safe to use np
to access that object.
As an illustration of when a weak_ptr
is useful, we’ll define a companion pointer class for our StrBlob
class. Our pointer class, which we’ll name StrBlobPtr
, will store a weak_ptr
to the data
member of the StrBlob
from which it was initialized. By using a weak_ptr
, we don’t affect the lifetime of the vector
to which a given StrBlob
points. However, we can prevent the user from attempting to access a vector
that no longer exists.
StrBlobPtr
will have two data members: wptr
, which is either null or points to a vector
in a StrBlob
; and curr
, which is the index of the element that this object currently denotes. Like its companion StrBlob
class, our pointer class has a check
member to verify that it is safe to dereference the StrBlobPtr
:
// StrBlobPtr throws an exception on attempts to access a nonexistent element
class StrBlobPtr {
public:
StrBlobPtr(): curr(0) { }
StrBlobPtr(StrBlob &a, size_t sz = 0):
wptr(a.data), curr(sz) { }
std::string& deref() const;
StrBlobPtr& incr(); // prefix version
private:
// check returns a shared_ptr to the vector if the check succeeds
std::shared_ptr<std::vector<std::string>>
check(std::size_t, const std::string&) const;
// store a weak_ptr, which means the underlying vector might be destroyed
std::weak_ptr<std::vector<std::string>> wptr;
std::size_t curr; // current position within the array
};
The default constructor generates a null StrBlobPtr
. Its constructor initializer list (§ 7.1.4, p. 265) explicitly initializes curr
to zero and implicitly initializes wptr
as a null weak_ptr
. The second constructor takes a reference to StrBlob
and an optional index value. This constructor initializes wptr
to point to the vector
in the shared_ptr
of the given StrBlob
object and initializes curr
to the value of sz
. We use a default argument (§ 6.5.1, p. 236) to initialize curr
to denote the first element by default. As we’ll see, the sz
parameter will be used by the end
member of StrBlob
.
It is worth noting that we cannot bind a StrBlobPtr
to a const StrBlob
object. This restriction follows from the fact that the constructor takes a reference to a nonconst
object of type StrBlob
.
The check
member of StrBlobPtr
differs from the one in StrBlob
because it must check whether the vector
to which it points is still around:
std::shared_ptr<std::vector<std::string>>
StrBlobPtr::check(std::size_t i, const std::string &msg) const
{
auto ret = wptr.lock(); // is the vector still around?
if (!ret)
throw std::runtime_error("unbound StrBlobPtr");
if (i >= ret->size())
throw std::out_of_range(msg);
return ret; // otherwise, return a shared_ptr to the vector
}
Because a weak_ptr
does not participate in the reference count of its corresponding shared_ptr
, the vector
to which this StrBlobPtr
points might have been deleted. If the vector
is gone, lock
will return a null pointer. In this case, any reference to the vector
will fail, so we throw an exception. Otherwise, check
verifies its given index. If that value is okay, check
returns the shared_ptr
it obtained from lock
.
We’ll learn how to define our own operators in Chapter 14. For now, we’ve defined functions named deref
and incr
to dereference and increment the StrBlobPtr
, respectively. The deref
member calls check
to verify that it is safe to use the vector
and that curr
is in range:
std::string& StrBlobPtr::deref() const
{
auto p = check(curr, "dereference past end");
return (*p)[curr]; // (*p) is the vector to which this object points
}
If check
succeeds, p
is a shared_ptr
to the vector
to which this StrBlobPtr
points. The expression (*p)[curr]
dereferences that shared_ptr
to get the vector
and uses the subscript operator to fetch and return the element at curr
.
The incr
member also calls check
:
// prefix: return a reference to the incremented object
StrBlobPtr& StrBlobPtr::incr()
{
// if curr already points past the end of the container, can't increment it
check(curr, "increment past end of StrBlobPtr");
++curr; // advance the current state
return *this;
}
We’ll also give our StrBlob
class begin
and end
operations. These members will return StrBlobPtrs
pointing to the first or one past the last element in the StrBlob
itself. In addition, because StrBlobPtr
accesses the data member of StrBlob
, wemust alsomake StrBlobPtr
a friend
of StrBlob
(§ 7.3.4, p. 279):
class StrBlob {
friend class StrBlobPtr;
// other members as in § 12.1.1 (p. 456)
StrBlobPtr begin(); // return StrBlobPtr to the first element
StrBlobPtr end(); // and one past the last element
};
// these members can’t be defined until StrStrBlob and StrStrBlobPtr are defined
StrBlobPtr StrBlob::begin() { return StrBlobPtr(*this); }
StrBlobPtr StrBlob::end()
{ return StrBlobPtr(*this, data->size()); }
Exercise 12.19: Define your own version of StrBlobPtr
and update your StrBlob
class with the appropriate friend
declaration and begin
and end
members.
Exercise 12.20: Write a program that reads an input file a line at a time into a StrBlob
and uses a StrBlobPtr
to print each element in that StrBlob
.
Exercise 12.21: We could have written StrBlobPtr
’s deref
member as follows:
std::string& deref() const
{ return (*check(curr, "dereference past end"))[curr]; }
Which version do you think is better and why?
Exercise 12.22: What changes would need to be made to StrBlobPtr
to create a class that can be used with a const StrBlob
? Define a class named ConstStrBlobPtr
that can point to a const StrBlob
.