Home Item 7 (2/2) - Smart pointers(English)
Post
Cancel

Item 7 (2/2) - Smart pointers(English)

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.

Desktop View

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.

Desktop View

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.

☝ツ☝

This post is licensed under CC BY 4.0 by the author.

👈 ツ 👍