乐闻世界logo
搜索文章和话题

What are the usage scenarios and differences of C++ smart pointers

2月18日 17:32

Usage Scenarios and Differences of C++ Smart Pointers

C++11 introduced smart pointers for automatic management of dynamically allocated memory, avoiding memory leaks and dangling pointer issues. Smart pointers are a typical application of the RAII (Resource Acquisition Is Initialization) pattern.

Three Main Smart Pointers

1. std::unique_ptr

  • Exclusive ownership smart pointer
  • Only one unique_ptr can point to an object at any given time
  • Cannot be copied, only moved
  • Lightweight with almost no overhead

2. std::shared_ptr

  • Shared ownership smart pointer
  • Multiple shared_ptrs can point to the same object
  • Uses reference counting to manage object lifetime
  • Can be copied and moved

3. std::weak_ptr

  • Smart pointer that doesn't control object lifetime
  • Must be used with shared_ptr
  • Used to solve circular reference problems with shared_ptr
  • Does not increase reference count

Usage Scenarios

std::unique_ptr applicable scenarios:

  • Object ownership is clear and doesn't need to be shared
  • Returning dynamically allocated objects as function return values
  • Storing dynamically allocated objects in containers
  • Pimpl (Pointer to Implementation) pattern

std::shared_ptr applicable scenarios:

  • Multiple objects need to share the same resource
  • Object lifetime is uncertain and needs delayed release
  • Passing objects in asynchronous programming
  • Sharing resources in cache systems

std::weak_ptr applicable scenarios:

  • Avoiding circular references in observer patterns
  • Avoiding strong references causing resource unavailability in cache systems
  • Breaking circular references in shared_ptr

Code Examples

std::unique_ptr example:

cpp
#include <memory> #include <iostream> class Widget { public: Widget() { std::cout << "Widget constructed\n"; } ~Widget() { std::cout << "Widget destroyed\n"; } void doSomething() { std::cout << "Widget doing something\n"; } }; // Create unique_ptr std::unique_ptr<Widget> ptr1 = std::make_unique<Widget>(); // Move semantics std::unique_ptr<Widget> ptr2 = std::move(ptr1); // ptr1 is now nullptr // Custom deleter auto deleter = [](Widget* w) { std::cout << "Custom deleter\n"; delete w; }; std::unique_ptr<Widget, decltype(deleter)> ptr3(new Widget(), deleter); // As function parameter void processWidget(std::unique_ptr<Widget> ptr) { ptr->doSomething(); } processWidget(std::move(ptr2));

std::shared_ptr example:

cpp
#include <memory> #include <iostream> class Resource { public: Resource() { std::cout << "Resource created\n"; } ~Resource() { std::cout << "Resource destroyed\n"; } }; // Create shared_ptr std::shared_ptr<Resource> ptr1 = std::make_shared<Resource>(); std::cout << "Use count: " << ptr1.use_count() << "\n"; // 1 // Copy std::shared_ptr<Resource> ptr2 = ptr1; std::cout << "Use count: " << ptr1.use_count() << "\n"; // 2 // Move std::shared_ptr<Resource> ptr3 = std::move(ptr1); std::cout << "Use count: " << ptr2.use_count() << "\n"; // 2 std::cout << "ptr1 is " << (ptr1 ? "not null" : "null") << "\n"; // null // Custom deleter auto deleter = [](Resource* r) { std::cout << "Custom deleter for Resource\n"; delete r; }; std::shared_ptr<Resource> ptr4(new Resource(), deleter);

std::weak_ptr example:

cpp
#include <memory> #include <iostream> class Node { public: std::shared_ptr<Node> next; std::weak_ptr<Node> prev; // Use weak_ptr to avoid circular reference Node() { std::cout << "Node created\n"; } ~Node() { std::cout << "Node destroyed\n"; } }; void createCycle() { auto node1 = std::make_shared<Node>(); auto node2 = std::make_shared<Node>(); node1->next = node2; node2->prev = node1; // weak_ptr doesn't increase reference count // node1 and node2 can be properly released } // Use weak_ptr to check if object exists std::weak_ptr<int> weakPtr; { auto sharedPtr = std::make_shared<int>(42); weakPtr = sharedPtr; if (auto locked = weakPtr.lock()) { std::cout << "Value: " << *locked << "\n"; // 42 } else { std::cout << "Object has been destroyed\n"; } } // sharedPtr goes out of scope, object is destroyed if (auto locked = weakPtr.lock()) { std::cout << "Value: " << *locked << "\n"; } else { std::cout << "Object has been destroyed\n"; // Executes here }

Performance Comparison

Memory overhead:

  • unique_ptr: Almost no overhead, only contains a raw pointer
  • shared_ptr: Contains two pointers (control block + object pointer), about 16 bytes
  • weak_ptr: Similar to shared_ptr, contains control block pointer

Operation overhead:

  • unique_ptr: All operations are O(1), no additional overhead
  • shared_ptr: Copy and destruction require atomic operations to modify reference count
  • weak_ptr: lock() operation requires atomic operations

Circular Reference Problem

Problem example:

cpp
class A { public: std::shared_ptr<B> b_ptr; }; class B { public: std::shared_ptr<A> a_ptr; // Causes circular reference }; auto a = std::make_shared<A>(); auto b = std::make_shared<B>(); a->b_ptr = b; b->a_ptr = a; // Reference counts of a and b never become 0, memory leak

Solution:

cpp
class B { public: std::weak_ptr<A> a_ptr; // Use weak_ptr to break the cycle };

Best Practices

1. Prefer std::make_unique and std::make_shared

cpp
// Recommended auto ptr = std::make_unique<Widget>(); auto ptr = std::make_shared<Resource>(); // Not recommended auto ptr = std::unique_ptr<Widget>(new Widget()); auto ptr = std::shared_ptr<Resource>(new Resource());

2. Use std::move to transfer unique_ptr ownership

cpp
std::unique_ptr<Widget> createWidget() { return std::make_unique<Widget>(); } auto ptr = createWidget(); // Move construction

3. Avoid creating shared_ptr from raw pointers

cpp
int* raw = new int(42); std::shared_ptr<int> ptr1(raw); std::shared_ptr<int> ptr2(raw); // Wrong! Will cause double deletion

4. Use std::enable_shared_from_this

cpp
class MyClass : public std::enable_shared_from_this<MyClass> { public: std::shared_ptr<MyClass> getShared() { return shared_from_this(); } }; auto obj = std::make_shared<MyClass>(); auto shared = obj->getShared();

Notes

  • Don't mix raw pointers and smart pointers to manage the same object
  • Be aware of thread safety when using shared_ptr in multi-threaded environments
  • weak_ptr::lock() can return an empty shared_ptr
  • Avoid frequently creating and destroying shared_ptr in loops
  • Consider using std::observer_ptr (C++26) as an alternative for non-owning pointers
标签:C++