C++11 智能指针
在 C++ 中,智能指针(Smart Pointers)是用来自动管理动态分配内存的工具,可以减少内存泄漏的风险。C++标准库提供了几种常见的智能指针,包括 std::unique_ptr
、std::shared_ptr
和 std::weak_ptr
,它们各自有不同的特性和用途。下面我们将详细对比解释这些智能指针。
std::unique_ptr
std::unique_ptr
是一种独占性的智能指针,意味着同一时刻只有一个 unique_ptr
可以拥有某个对象的所有权。它遵循 RAII(Resource Acquisition Is Initialization)原则,==当 unique_ptr
被销毁时,它所管理的对象会被自动释放。==
其特点:
- 独占所有权:同一个对象只能被一个
unique_ptr
拥有。 - 不能拷贝:==
unique_ptr
不能被复制,因为它拥有唯一的所有权。尝试拷贝会导致编译错误。== - 支持移动语义:可以通过移动构造函数或者移动赋值操作符==将所有权从一个
unique_ptr
移动到另一个==。(?) - 自动释放内存:当
unique_ptr
被销毁时,它会自动释放所管理的对象。- 注意结合 std:: make_unique 和 std:: move 使用!
#include <iostream> #include <memory> class MyClass { public: MyClass(int val) : value(val) { std::cout << "MyClass constructed with value: " << value << std::endl; } ~MyClass() { std::cout << "MyClass destructed with value: " << value << std::endl; } void showValue() const { std::cout << "Value: " << value << std::endl; } private: int value; }; int main() { std::unique_ptr<MyClass> ptr1 = std::make_unique<MyClass>(10); // ptr1 拥有 MyClass 的所有权 std::cout << "Moving ownership to ptr2..." << std::endl; std::unique_ptr<MyClass> ptr2 = std::move(ptr1); // 转移所有权 // ptr1 现在为空,不能再访问对象 if (ptr1) { ptr1->showValue(); } else { std::cout << "ptr1 is now null." << std::endl; } // ptr2 现在拥有 MyClass 对象的所有权 ptr2->showValue(); return 0; }
std::shared_ptr
std::shared_ptr
是一种共享所有权的智能指针,多个shared_ptr
可以共享同一个对象的所有权。只有当最后一个shared_ptr
被销毁时,所管理的对象才会被释放。
特点:
- 注意结合 std:: make_unique 和 std:: move 使用!
- 共享所有权:多个
shared_ptr
可以指向同一个对象,==每个shared_ptr
都会增加一个引用计数。== - 引用计数:
shared_ptr
会跟踪有多少个shared_ptr
指向同一个对象,当引用计数归零时,对象会被自动销毁。 - 拷贝与赋值:
shared_ptr
支持拷贝和赋值操作,每次拷贝都会增加引用计数。 - 性能开销:由于引用计数的管理,
shared_ptr
会带来额外的性能开销(例如原子操作)。释放需要手动吗?
#include <iostream> #include <memory> class MyClass { public: MyClass(int x) : x_(x) { std::cout << "MyClass(" << x_ << ") constructed\n"; } ~MyClass() { std::cout << "MyClass(" << x_ << ") destructed\n"; } private: int x_; }; int main() { std::shared_ptr<MyClass> ptr1 = std::make_shared<MyClass>(10); { std::shared_ptr<MyClass> ptr2 = ptr1; // 引用计数增加 std::cout << "Inside inner scope\n"; } // ptr2 离开作用域,引用计数减少 std::cout << "Outside inner scope\n"; return 0; }
说明:
ptr1
和ptr2
共享对同一个MyClass
对象的所有权。- 当
ptr2
离开作用域时,它的引用计数减少,但对象不会立即销毁,因为ptr1
仍然指向它。 - 当
ptr1
离开作用域时,引用计数归零,MyClass
对象被销毁。
问题:shared_ptr 引起循环计数问题:#include <iostream> #include <memory> class A; class B { public: std::shared_ptr<A> a_ptr; B() { std::cout << "B constructed\n"; } ~B() { std::cout << "B destructed\n"; } }; class A { public: std::shared_ptr<B> b_ptr; A() { std::cout << "A constructed\n"; } ~A() { std::cout << "A destructed\n"; } }; int main() { { std::shared_ptr<A> a = std::make_shared<A>(); std::shared_ptr<B> b = std::make_shared<B>(); a->b_ptr = b; // A -> B b->a_ptr = a; // B -> A // A 和 B 形成循环引用,内存无法释放 } // 这里的 a 和 b 应该销毁,但因为循环引用,它们的引用计数永远不为 0 std::cout << "End of scope\n"; return 0; }
std::weak_ptr
std::weak_ptr
是一种弱引用的智能指针,它与shared_ptr
配合使用,用来避免循环引用。weak_ptr
不会改变引用计数,它只提供对shared_ptr
所管理对象的访问。如果对象被销毁,weak_ptr
就不再有效。
特点: - 不影响引用计数:
weak_ptr
不会增加或减少引用计数,它只是一个“观察者”。 - 用于打破循环引用:
weak_ptr
主要用于解决shared_ptr
引起的循环引用问题。例如,==当两个对象通过shared_ptr
相互引用时,会导致内存无法释放。使用weak_ptr
可以避免这种情况。== - 转换为
shared_ptr
:weak_ptr
可以通过lock()
方法转换为shared_ptr
。如果对象已经被销毁,lock()
返回一个空的shared_ptr
。
使用 weak_ptr 打破循环引用:#include <iostream> #include <memory> class A; class B { public: std::weak_ptr<A> a_ptr; // 使用 weak_ptr 避免循环引用 B() { std::cout << "B constructed\n"; } ~B() { std::cout << "B destructed\n"; } }; class A { public: std::shared_ptr<B> b_ptr; A() { std::cout << "A constructed\n"; } ~A() { std::cout << "A destructed\n"; } }; int main() { { std::shared_ptr<A> a = std::make_shared<A>(); std::shared_ptr<B> b = std::make_shared<B>(); a->b_ptr = b; // A -> B b->a_ptr = a; // B -> A,但这里是 weak_ptr,避免了循环引用 // 由于没有循环引用,内存可以正确释放 } // A 和 B 都会被销毁 std::cout << "End of scope\n"; return 0; }
对比总结
特性 | std::unique_ptr | std::shared_ptr | std::weak_ptr |
---|---|---|---|
所有权管理 | 独占所有权 | 共享所有权 | 不拥有对象,辅助管理共享所有权 |
拷贝行为 | 不支持拷贝,只能移动 | 支持拷贝,增加引用计数 | 不影响引用计数,只是弱引用 |
引用计数 | 无引用计数 | 有引用计数,自动销毁对象 | 无引用计数,仅观察对象是否仍存在 |
适用场景 | 需要独占所有权,简单的对象管理 | 需要共享所有权的复杂对象管理 | 解决 shared_ptr 引起的循环引用问题 |
性能开销 | 最小,只有对象本身的管理 | 稍大,包含引用计数的管理开销 | 极小,仅提供观察者功能 |
评论