C++: Smart pointers, part 5: weak_ptr

This is the last of several posts I wrote related to smart pointers:

  1. Smart pointers
  2. unique_ptr
  3. More on unique_ptr
  4. shared_ptr
  5. weak_ptr

In modern C++ applications (C++11 and later), you can replace almost all your naked pointers to shared_ptr and unique_ptr in order to have automatic resource administration in a deterministic way so you will not need (almost, again) to release the memory manually.

The “almost” means that there is one scenario where the smart pointers, specifically, the shared_ptr instances, will not work: When you have circular references. In this scenario, since every shared_ptr is pointing to the other one, the memory will never be released.

Let’s look this scenario in code:

struct Child;

struct Parent
{
	shared_ptr<Child> child;
	
	~Parent() { cout << "Bye Parent" << endl; }
	
	void hi() const { cout << "Hello" << endl; }
};

struct Child
{
	shared_ptr<Parent> parent;
	
	~Child() { cout << "Bye Child" << endl; }
};

int main()
{
	auto parent = make_shared<Parent>();
	auto child = make_shared<Child>();
	parent->child = child;
	child->parent = parent;
	child->parent->hi();
}

In this program, the Parent and the Child have pointers to their respective child and
parent (creating a circular reference between both); when you execute the program, you will notice the destructors are never invoked because I used shared_ptr instances.

If we model this problem, probably we can establish the Parent as “owner” of the Child object (it would mean that the child lifetime will be based on the parent lifetime) and the Child will just refer to its parent. If we diagram this using a class diagram, we can draw it like this:

weak_ptr

If we use C++11 smart pointers to implement this kind of scenarios, we could use shared_ptr instances to implement the composition relationship and weak_ptr instances to implement the aggregation relationship:

struct Child;

struct Parent
{
	shared_ptr<Child> child;
	
	~Parent() { cout << "Bye Parent" << endl; }
	
	void hi() const { cout << "Hello" << endl; }
};

struct Child
{
	weak_ptr<Parent> parent;
	
	~Child() { cout << "Bye Child" << endl; }
};

int main()
{
	auto parent = make_shared<Parent>();
	auto child = make_shared<Child>();
	parent->child = child;
	child->parent = parent;
	child->parent.lock()->hi();
}

When you execute this program, you will notice the destructors are invoked correctly.

weak_ptr is a wrapper for a shared_ptr that does not represent ownership of the pointee object and, therefore, does not avoid the object to be released when the parent shared_ptr reference counter goes to 0.

In my code I am using a weak_ptr method called lock. weak_ptr does not implement the operator->, so, if you want to access the methods of the pointee object, you need to invoke the lock(9 method. It creates a temporary shared_ptr instance that, since it increases the pointee reference counter, actually locks the object to be released while it is executing the invoked method.

Other nice feature of weak_ptr is that it can tell you if its pointee object has been already released. Look at this code:

struct A
{
	int x;	
	A(int x)  : x(x) { cout << "HI" << endl; }
	~A() { cout << "Bye" << endl; }
};

weak_ptr<A> m()
{
	auto a = make_shared<A>(12);
	cout << a->x << endl;
	
	return a;
}

int main()
{
	auto a = m();
	
	cout << "After m()" << endl;
	
	if (a.expired())
		cout << "Expired" << endl;
	else
		cout << a.lock()->x << endl;
}

The function main() receives an instance of a weak_ptr. If you look into the code, the function m() has a shared_ptr instance, but the object pointed by it lives only inside the function and it is released when returning the weak_ptr. Back to the main() function, before accessing the value of x, I am invoking the method expired() that returns true if the weak_ptr instance points to an already released method.

Thus, with unique_ptr, shared_ptr, and weak_ptr, all the scenarios where you need to use dynamically allocated memory are covered and you will not need to release your objects manually anymore.

2 thoughts on “C++: Smart pointers, part 5: weak_ptr

  1. In your `final` Parent-Child model, you create Child as a shared_ptr in Parent. But Parent owns Child. That makes the model seem a bit odd, doesn’t it?

  2. therefore, does not avoid the object to be released when the parent shared_ptr reference counter goes to 0. Is this is a correct prase?

Leave a comment