Sobes.tech
Middle

How does the reference counter work in std::shared_ptr?

sobes.tech AI

Answer from AI

The reference counter in std::shared_ptr is stored in a separate object — the control block.

The control block is created:

  • When the first std::shared_ptr is created from a pointer.
  • When creating a std::shared_ptr with a custom deleter or allocator.
  • When using std::make_shared or std::allocate_shared.

This control block contains at least two counters:

  1. Strong reference count: Increases when creating or copying a std::shared_ptr. Decreases when destroying a std::shared_ptr. When this counter reaches zero, the managed object is released.
  2. Weak reference count: Increases when creating a std::weak_ptr from a std::shared_ptr. Decreases when destroying a std::weak_ptr. The control block is released when both counters — strong and weak — reach zero.

Using std::make_shared is preferable over direct creation with new, as it can allocate the object and control block in a single memory block, improving performance and reducing fragmentation.

Counters are atomic, making std::shared_ptr safe for use in multithreaded scenarios, although access to the managed object itself is not synchronized by default.

Example:

#include <iostream>
#include <memory>

struct MyObject {
    int data;
    MyObject(int d) : data(d) {
        std::cout << "MyObject(" << data << ") created\n";
    }
    ~MyObject() {
        std::cout << "MyObject(" << data << ") destroyed\n";
    }
};

int main() {
    std::shared_ptr<MyObject> ptr1 = std::make_shared<MyObject>(10);
    // strong count: 1, weak count: 0

    {
        std::shared_ptr<MyObject> ptr2 = ptr1;
        // strong count: 2, weak count: 0

        std::weak_ptr<MyObject> weak_ptr = ptr1;
        // strong count: 2, weak count: 1

        if (auto locked_ptr = weak_ptr.lock()) {
            // strong count: 3, weak count: 1 (inside lock, temporarily)
            std::cout << "Object data: " << locked_ptr->data << std::endl;
        }
        // strong count: 2, weak count: 1
    } // ptr2 goes out of scope
    // strong count: 1, weak count: 1

    // The object is not yet destroyed, as strong count == 1

    {
        std::weak_ptr<MyObject> weak_ptr2 = ptr1;
        // strong count: 1, weak count: 2
    } // weak_ptr2 goes out of scope
    // strong count: 1, weak count: 1

} // ptr1 goes out of scope
// strong count: 0, weak count: 1
// The object is destroyed as the strong count reached zero
// The control block remains until the weak count reaches zero