C++ 智能指针的使用场景和区别
C++11 引入了智能指针,用于自动管理动态分配的内存,避免内存泄漏和悬空指针问题。智能指针是 RAII(资源获取即初始化)模式的典型应用。
三种主要智能指针
1. std::unique_ptr
- 独占所有权的智能指针
- 同一时刻只能有一个 unique_ptr 指向对象
- 不可复制,只能移动
- 轻量级,几乎没有额外开销
2. std::shared_ptr
- 共享所有权的智能指针
- 多个 shared_ptr 可以指向同一个对象
- 使用引用计数管理对象生命周期
- 可以复制和移动
3. std::weak_ptr
- 不控制对象生命周期的智能指针
- 必须与 shared_ptr 配合使用
- 用于解决 shared_ptr 的循环引用问题
- 不增加引用计数
使用场景
std::unique_ptr 适用场景:
- 对象的所有权明确,不需要共享
- 作为函数返回值返回动态分配的对象
- 容器中存储动态分配的对象
- Pimpl(Pointer to Implementation)模式
std::shared_ptr 适用场景:
- 多个对象需要共享同一个资源
- 对象的生命周期不确定,需要延迟释放
- 异步编程中传递对象
- 缓存系统中共享资源
std::weak_ptr 适用场景:
- 观察者模式中避免循环引用
- 缓存系统中避免强引用导致资源无法释放
- 打破 shared_ptr 的循环引用
代码示例
std::unique_ptr 示例:
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"; } }; // 创建 unique_ptr std::unique_ptr<Widget> ptr1 = std::make_unique<Widget>(); // 移动语义 std::unique_ptr<Widget> ptr2 = std::move(ptr1); // ptr1 现在为 nullptr // 自定义删除器 auto deleter = [](Widget* w) { std::cout << "Custom deleter\n"; delete w; }; std::unique_ptr<Widget, decltype(deleter)> ptr3(new Widget(), deleter); // 作为函数参数 void processWidget(std::unique_ptr<Widget> ptr) { ptr->doSomething(); } processWidget(std::move(ptr2));
std::shared_ptr 示例:
cpp#include <memory> #include <iostream> class Resource { public: Resource() { std::cout << "Resource created\n"; } ~Resource() { std::cout << "Resource destroyed\n"; } }; // 创建 shared_ptr std::shared_ptr<Resource> ptr1 = std::make_shared<Resource>(); std::cout << "Use count: " << ptr1.use_count() << "\n"; // 1 // 复制 std::shared_ptr<Resource> ptr2 = ptr1; std::cout << "Use count: " << ptr1.use_count() << "\n"; // 2 // 移动 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 // 自定义删除器 auto deleter = [](Resource* r) { std::cout << "Custom deleter for Resource\n"; delete r; }; std::shared_ptr<Resource> ptr4(new Resource(), deleter);
std::weak_ptr 示例:
cpp#include <memory> #include <iostream> class Node { public: std::shared_ptr<Node> next; std::weak_ptr<Node> prev; // 使用 weak_ptr 避免循环引用 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 不会增加引用计数 // node1 和 node2 可以正常释放 } // 使用 weak_ptr 检查对象是否存在 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 离开作用域,对象被销毁 if (auto locked = weakPtr.lock()) { std::cout << "Value: " << *locked << "\n"; } else { std::cout << "Object has been destroyed\n"; // 执行这里 }
性能比较
内存开销:
- unique_ptr:几乎无额外开销,只包含一个原始指针
- shared_ptr:包含两个指针(控制块 + 对象指针),约 16 字节
- weak_ptr:与 shared_ptr 类似,包含控制块指针
操作开销:
- unique_ptr:所有操作都是 O(1),无额外开销
- shared_ptr:复制和销毁需要原子操作修改引用计数
- weak_ptr:lock() 操作需要原子操作
循环引用问题
问题示例:
cppclass A { public: std::shared_ptr<B> b_ptr; }; class B { public: std::shared_ptr<A> a_ptr; // 导致循环引用 }; auto a = std::make_shared<A>(); auto b = std::make_shared<B>(); a->b_ptr = b; b->a_ptr = a; // a 和 b 的引用计数永远不会变为 0,内存泄漏
解决方案:
cppclass B { public: std::weak_ptr<A> a_ptr; // 使用 weak_ptr 打破循环 };
最佳实践
1. 优先使用 std::make_unique 和 std::make_shared
cpp// 推荐 auto ptr = std::make_unique<Widget>(); auto ptr = std::make_shared<Resource>(); // 不推荐 auto ptr = std::unique_ptr<Widget>(new Widget()); auto ptr = std::shared_ptr<Resource>(new Resource());
2. 使用 std::move 转移 unique_ptr 所有权
cppstd::unique_ptr<Widget> createWidget() { return std::make_unique<Widget>(); } auto ptr = createWidget(); // 移动构造
3. 避免从裸指针创建 shared_ptr
cppint* raw = new int(42); std::shared_ptr<int> ptr1(raw); std::shared_ptr<int> ptr2(raw); // 错误!会导致双重释放
4. 使用 std::enable_shared_from_this
cppclass 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();
注意事项
- 不要混用裸指针和智能指针管理同一个对象
- 在多线程环境中使用 shared_ptr 时要注意线程安全
- weak_ptr::lock() 返回的 shared_ptr 可能为空
- 避免在循环中频繁创建和销毁 shared_ptr
- 考虑使用 std::observer_ptr(C++26)作为非拥有指针的替代方案