std::weak_ptr: Smart pointer to observe but not own an object managed by other smart pointers.
A std::weak_ptr allows you to observe an object managed by one or more std::shared_ptrs without participating in its ownership. This means it doesn't increment the reference count, and therefore doesn't keep the object alive by itself. A weak_ptr can be used to query whether the object still exists, and if it does, a temporary std::shared_ptr can be obtained to safely access the object as long as that temporary owner is in scope.
The primary use case for a weak_ptr is to break reference cycles. A reference cycle occurs when two or more objects own each other via std::shared_ptrs. As a result, their reference counts never drop to zero, the objects are never deleted, and their memory is leaked.
A typical reference cycle looks like this:
#include <iostream>
#include <memory>
class B;
class A
{
public:
std::shared_ptr<B> b_ptr;
// Run this destructor when the object lifetime ends
~A()
{
std::cout << "A destroyed\n";
}
};
class B
{
public:
std::shared_ptr<A> a_ptr;
~B()
{
std::cout << "B destroyed\n";
}
};
int main()
{
std::shared_ptr<A> a{ std::make_shared<A>() };
std::shared_ptr<B> b{ std::make_shared<B>() };
a->b_ptr = b;
b->a_ptr = a;
return 0;
}
The cycle is broken by changing one of the owning pointers to be non owning:
#include <iostream>
#include <memory>
class B;
class A
{
public:
std::shared_ptr<B> b_ptr;
~A()
{
std::cout << "A destroyed\n";
}
};
class B
{
public:
std::weak_ptr<A> a_ptr;
~B()
{
std::cout << "B destroyed\n";
}
};
int main()
{
// Create the object with shared ownership
std::shared_ptr<A> a{ std::make_shared<A>() };
std::shared_ptr<B> b{ std::make_shared<B>() };
a->b_ptr = b;
b->a_ptr = a;
return 0;
}
A weak_ptr cannot directly access the object it observes. Instead, it must first be "locked" into a shared_ptr using the lock() member function. If the object still exists, lock() returns a non empty shared_ptr which can be used to access the object. If the object has already been destroyed, lock() returns an empty shared_ptr.
#include <iostream>
#include <memory>
class Tracer
{
public:
void hello() const
{
std::cout << "hello\n";
}
~Tracer()
{
std::cout << "Tracer destroyed\n";
}
};
int main()
{
std::weak_ptr<Tracer> wp;
{
std::shared_ptr<Tracer> sp{ std::make_shared<Tracer>() };
wp = sp;
// Check this condition before entering the block
if (std::shared_ptr<Tracer> locked{ wp.lock() })
locked->hello();
}
if (std::shared_ptr<Tracer> locked{ wp.lock() })
std::cout << "object still alive\n";
else
std::cout << "object already destroyed\n";
return 0;
}
In summary, std::weak_ptr provides a safe, non owning way to observe objects managed by std::shared_ptr.
Its ability to break reference cycles prevents memory leaks, and its lock() function allows you to safely access the observed object if it's still alive, for the duration of a specific operation.
Exercise: Using std::weak_ptr to Prevent Reference CyclesStarter code, hints and solutionOpenClose
Practice: Using std::weak_ptr to Prevent Reference Cycles
Work through the mission, reveal hints only when needed, and compare your result with the model solution.
Your challenge
After the owner block ends, report wp.expired() and whether wp.lock() succeeds. Explain why a weak observer cannot keep the object alive.
Mission checklist
- Run the starter as a baseline, then predict the change requested in Your challenge.
- Make that change in your editor; use the lesson-specific hints if you need help.
- Compile and run your answer, then compare with the worked solution and explain the result.
Starter program
This is the working baseline, not the completed answer. Compile as C++20, then make the change described above.
#include <iostream>
#include <memory>
class Tracer
{
public:
void hello() const
{
std::cout << "hello\n";
}
~Tracer()
{
std::cout << "Tracer destroyed\n";
}
};
int main()
{
std::weak_ptr<Tracer> wp;
{
std::shared_ptr<Tracer> sp{ std::make_shared<Tracer>() };
wp = sp;
// Check this condition before entering the block
if (std::shared_ptr<Tracer> locked{ wp.lock() })
locked->hello();
}
if (std::shared_ptr<Tracer> locked{ wp.lock() })
std::cout << "object still alive\n";
else
std::cout << "object already destroyed\n";
return 0;
}Hints
Assigning sp to wp creates an observer, not another shared owner. A successful wp.lock() temporarily creates an owning shared_ptr named locked.
Compare the first lock inside sp's scope with the second lock after it. Once the last shared owner is gone, lock returns an empty pointer rather than reviving the object.
Worked solution
weak_ptr only observes the object owned by sp. A successful lock creates a temporary shared owner; after the block both the original owner and temporary are gone. The added expiry report and failed lock confirm that wp did not extend the object lifetime.
#include <iostream>
#include <memory>
class Tracer
{
public:
void hello() const
{
std::cout << "hello\n";
}
~Tracer()
{
std::cout << "Tracer destroyed\n";
}
};
int main()
{
std::weak_ptr<Tracer> wp;
{
std::shared_ptr<Tracer> sp{ std::make_shared<Tracer>() };
wp = sp;
// Check this condition before entering the block
if (std::shared_ptr<Tracer> locked{ wp.lock() })
locked->hello();
}
if (std::shared_ptr<Tracer> locked{ wp.lock() })
std::cout << "object still alive\n";
else
std::cout << "object already destroyed\n";
std::cout << "Expired after block: " << wp.expired() << '\n';
std::cout << "Lock succeeds: " << static_cast<bool>(wp.lock()) << '\n';
return 0;
}
Example output
hello
Tracer destroyed
object already destroyed
Expired after block: 1
Lock succeeds: 0
0 comments
No comments yet. Be the first.