std::shared_ptr Circular Reference Issue
Is std::shared_ptr
smart enough for us to use it without worries? No!!! Although std::shared_ptr
is indeed very useful, careless use can still lead to problems!
The circular reference issue with std::shared_ptr occurs when two or more std::shared_ptr objects reference each other, leading to the resources they manage not being released, causing memory leaks. This happens because each std::shared_ptr increases the reference count of the pointed-to object, and the object is only destroyed when the reference count reaches zero.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#include <iostream>
#include <memory>
class B; // Forward declaration
class A {
public:
std::shared_ptr<B> b_ptr;
A() { std::cout << "A created : " << this << "\n"; }
~A() { std::cout << "A destroyed\n"; }
};
class B {
public:
std::shared_ptr<A> a_ptr;
B() { std::cout << "B created : " << this << "\n"; }
~B() { std::cout << "B destroyed\n"; }
};
int main() {
std::cout << "~Enter scope" << std::endl;
{
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;
std::cout << "a.use_count : " << a.use_count() << std::endl;
std::cout << "b.use_count : " << b.use_count() << std::endl;
// Neither A nor B will be destroyed at the end of this scope due to circular reference
}
std::cout << "~Leave scope" << std::endl;
return 0;
}
Execution result: You can see that two objects were created, but when leaving the scope, neither of the objects was automatically released by the smart pointers. This results in a memory leak.
std::weak_ptr
Here are some key points about std::weak_ptr
.
Definition and Initialization:
A std::weak_ptr
cannot be created on its own; it must be initialized from a std::shared_ptr object.
1
2
std::shared_ptr<int> sp = std::make_shared<int>(3);
std::weak_ptr<int> wp = sp;
Does not affect reference count: A std::weak_ptr
does not increase the reference count of the managed object, so it does not prevent the managed object from being destroyed.
Using weak_ptr to rewrite the above example.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#include <iostream>
#include <memory>
class B; // Forward declaration
class A {
public:
std::shared_ptr<B> b_ptr;
A() { std::cout << "A created : " << this << "\n"; }
~A() { std::cout << "A destroyed\n"; }
};
class B {
public:
std::weak_ptr<A> a_ptr; // !!!!!!!!!!!!
B() { std::cout << "B created : " << this << "\n"; }
~B() { std::cout << "B destroyed\n"; }
};
int main() {
std::cout << "~Enter scope" << std::endl;
{
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;
std::cout << "a.use_count : " << a.use_count() << std::endl;
std::cout << "b.use_count : " << b.use_count() << std::endl;
// Neither A nor B will be destroyed at the end of this scope due to circular reference
}
std::cout << "~Leave scope" << std::endl;
return 0;
}
Only change a_ptr in class B to weak_ptr
.
Execution result:
You can see that both objects were correctly released. And a.use_count is 1.
Usage Scenarios
std::weak_ptr
is typically used to implement the observer pattern, avoid circular references, or in situations where you need to access but not own a resource.