Sobes.tech
Middle

How is reference counting implemented in shared_ptr?

sobes.tech AI

Answer from AI

shared_ptr uses an internal control block that contains a strong reference count and a weak reference count.

Working mechanism:

  1. Creation: When the first shared_ptr pointing to an object is created, a control block is created with both counters initialized to 1.
    #include <memory>
    
    int main() {
        auto ptr = std::make_shared<int>(10); // Control block created, strong_count = 1, weak_count = 1 (pointer in control block also counted)
        return 0;
    }
    
  2. Copying shared_ptr: When copying a shared_ptr, the strong_count in the same control block is increased.
    #include <memory>
    
    int main() {
        auto ptr1 = std::make_shared<int>(10); // strong_count = 1
        auto ptr2 = ptr1;                     // strong_count increases to 2
        return 0;
    }
    
  3. Assigning shared_ptr: Assigning one shared_ptr to another decreases the strong_count of the left operand (if it pointed to an object) and increases the strong_count of the right operand.
  4. Creating weak_ptr: When creating a weak_ptr from a shared_ptr, the weak_count in the same control block is increased. The strong_count remains unchanged.
    #include <memory>
    #include <weak_ptr>
    
    int main() {
        auto ptr = std::make_shared<int>(10); // strong_count = 1, weak_count = 1
        std::weak_ptr<int> weak_ptr = ptr;   // weak_count increases to 2
        return 0;
    }
    
  5. Exiting scope or resetting: When a shared_ptr is destroyed (e.g., when leaving scope or calling reset()), the strong_count decreases.
    #include <memory>
    
    int main() {
        {
            auto ptr = std::make_shared<int>(10); // strong_count = 1
        } // strong_count decreases to 0
        return 0;
    }
    
    #include <memory>
    
    int main() {
        auto ptr = std::make_shared<int>(10); // strong_count = 1
        ptr.reset();                          // strong_count decreases to 0
        return 0;
    }
    
  6. Releasing the object: When strong_count becomes 0, the object pointed to by shared_ptr is deleted.
  7. Releasing the control block: When both strong_count and weak_count are 0, the control block is deleted.
  8. Exiting scope or resetting weak_ptr: When a weak_ptr is destroyed, the weak_count decreases.
    #include <memory>
    #include <weak_ptr>
    
    int main() {
        auto ptr = std::make_shared<int>(10); // strong_count = 1, weak_count = 1
        {
            std::weak_ptr<int> weak_ptr = ptr; // weak_count = 2
        } // weak_count decreases to 1
        return 0;
    }
    

Reference counting is thread-safe due to atomic operations on counters within the control block.

Example of a control block structure (simplified):

struct ControlBlock {
    std::atomic<long> strong_count{1};
    std::atomic<long> weak_count{1}; // Initially 1, as the control block holds its pointer
    // Additional info such as deleter, allocator
};