59 C++ 现代C++编程艺术8-智能指针
C++ 现代C++编程艺术8-智能指针
文章目录
- C++ 现代C++编程艺术8-智能指针
- 一 智能指针原理
- 二、`unique_ptr`独占指针(独占所有权)
- 三、`shared_ptr`共享指针(共享所有权)
- 四、`weak_ptr`弱引用指针(观察者指针)
- 五、 std::make_shared内存分配
- 六、 智能指针的高级用法
- 七、 选择智能指针的准则
一 智能指针原理
裸指针: 原生指针,int *p = new int(10)
- 提前释放和忘记释放都可造成错误。
智能指针: 用类模板对指针进行自动管理。
-
智能指针原理
用简单的示例来说明智能指针的原理#include <iostream>template<typename Ty> class Auto_Ptr { public://不能进行赋值,还能通过构造函数来初始化explicit Auto_Ptr(Ty* ptr):ptr(ptr){}// 禁止拷贝构造和赋值Auto_Ptr(const Auto_Ptr&) = delete;Auto_Ptr& operator=(const Auto_Ptr&) = delete;~Auto_Ptr(){if(nullptr != ptr){delete ptr;ptr = nullptr; // 避免悬空指针 std::cout<<"指针已释放!"<<std::endl;}}//指针取值操作Ty& operator*(){return *ptr;}//->成员访问指针操作符Ty* operator->(){return ptr;}private:Ty* ptr; };// 定义一个测试类,用于演示->操作符 class TestClass { public:int value;std::string name;void printInfo() {std::cout << "Name: " << name << ", Value: " << value << std::endl;} };int main() {Auto_Ptr<int> dat(new int(10));std::cout<<*dat<<std::endl;//输出: 10 指针已释放!// 类类型使用示例 Auto_Ptr<TestClass> objPtr(new TestClass());// 使用->操作符访问成员变量 objPtr->value = 42;objPtr->name = "智能指针测试";// 使用->操作符调用成员函数 objPtr->printInfo(); //输出: Name: 智能指针测试, Value: 42// 也可以这样使用,但不太常见(*objPtr).value = 100;(*objPtr).printInfo();//输出: Name: 智能指针测试, Value: 100// 指针已释放return 0; }
二、unique_ptr
独占指针(独占所有权)
▶ 特点:
-
唯一所有权:同一时间只能有一个
unique_ptr
指向资源,独占所有权,不能复制,只能移动 -
零额外开销:无引用计数机制,性能接近裸指针
-
支持自定义删除器(如管理文件句柄、网络连接等)
-
适用于资源独占场景
▶ 示例代码:#include <iostream> #include <memory>class TestClass { public:int value;std::string name;void printInfo() {std::cout << "Name: " << name << ", Value: " << value << std::endl;} };int main() {auto ptr = std::make_unique<int>(42); // C++14推荐创建方式 std::unique_ptr<TestClass> obj(new TestClass());// 使用指针 obj->printInfo();(*obj).printInfo();// 所有权转移 auto newOwner = std::move(ptr); // 原ptr变为nullptr // 管理数组 auto arr = std::make_unique<int[]>(5);arr[0] = 10;// 自定义删除器(管理文件)std::unique_ptr<FILE, decltype(&fclose)> filePtr(fopen("data.txt", "r"), fclose);// 自动释放内存 return 0; }
三、shared_ptr
共享指针(共享所有权)
▶ 特点:
-
引用计数:多个指针共享资源(多个指针可以共享同一对象),计数归零时自动释放
-
支持弱引用:需配合
weak_ptr
打破循环引用 -
控制块存储计数:推荐用
make_shared
合并内存分配
▶ 示例代码#include <iostream> #include <memory>class TestClass { public:int value;std::string name;void printInfo() {std::cout << "Name: " << name << ", Value: " << value << std::endl;} };int main() {auto sp1 = std::make_shared<TestClass>();std::shared_ptr<TestClass> sp2 = sp1; // 引用计数+1 std::cout << "引用计数: " << sp1.use_count() << std::endl;//引用计数: 2std::shared_ptr<TestClass> sp3 = sp1; // 引用计数+1 std::cout << "引用计数: " << sp1.use_count() << std::endl;//引用计数: 3{std::shared_ptr<TestClass> sp4 = sp1; // 引用计数+1 std::cout << "引用计数: " << sp1.use_count() << std::endl;//引用计数: 4}//sp4 离开作用域,引用计数减少 std::cout << "引用计数: " << sp1.use_count() << std::endl;//引用计数: 3// 循环引用问题(错误示例)struct Node {std::shared_ptr<Node> next; // 相互持有导致内存泄漏 };auto nodeA = std::make_shared<Node>();auto nodeB = std::make_shared<Node>();nodeA->next = nodeB;nodeB->next = nodeA;// 使用weak_ptr打破循环 struct SafeNode {std::weak_ptr<SafeNode> next; // 弱引用不增加计数 };// 最后一个shared_ptr销毁时释放对象 return 0; }
四、weak_ptr
弱引用指针(观察者指针)
▶ 特点
- 不拥有所有权:不影响引用计数
- 必须转换为
shared_ptr
才能访问对象 - 提供
expired()
方法检测资源有效性 - 用于解决
shared_ptr
的循环引用问题
▶ 示例代码
#include <iostream>
#include <memory>
#include <vector>class TestClass {
public:int value;std::string name;void printInfo() {std::cout << "Name: " << name << ", Value: " << value << std::endl;}
};int main() {auto shared = std::make_shared<int>(100);std::weak_ptr<int> weak = shared;if (!weak.expired()) {if (auto temp = weak.lock()) { // 转换为shared_ptr *temp = 200; // 安全访问 }}using Resource = TestClass;// 典型应用:对象池模式 class ObjectPool {std::vector<std::weak_ptr<Resource>> pool;public:std::shared_ptr<Resource> acquire() {for (auto& wp : pool) {if (auto sp = wp.lock()) return sp;}return std::shared_ptr<Resource>(new Resource);}};// 最后一个shared_ptr销毁时释放对象
return 0;
}
五、 std::make_shared内存分配
基本用法
auto ptr = std::make_shared<Type>(args...);
- Type 是要创建的对象类型
- args… 是传递给 Type 构造函数的参数
- 使用示例
make_shared
通常比直接使用 shared_ptr
构造函数更高效,因为:
-
它只需要一次内存分配(传统方式需要两次)
-
它将对象和控制块(包含引用计数等)分配在连续内存中
//传统方式: std::shared_ptr<Widget> spw(new Widget); // 两次分配:对象和控制块//make_shared 方式: auto spw = std::make_shared<Widget>(); // 一次分配
-
基本类型用法
auto intPtr = std::make_shared<int>(42); std::cout << *intPtr << std::endl; // 输出: 42
-
类对象用法
class MyClass { public:MyClass(int x, std::string s) : x(x), s(s) {}void print() { std::cout << s << ": " << x << std::endl; } private:int x;std::string s; };auto objPtr = std::make_shared<MyClass>(10, "value"); objPtr->print(); // 输出: value: 10
六、 智能指针的高级用法
-
自定义删除器
#include <memory> #include <iostream> #include <cstdio>void fileDeleter(FILE* file) {if (file) {fclose(file);std::cout << "File closed\n";} }int main() {// 使用自定义删除器管理文件 std::unique_ptr<FILE, decltype(&fileDeleter)> filePtr(fopen("test.txt", "r"), fileDeleter);if (filePtr) {char buffer[100];fgets(buffer, 100, filePtr.get()); std::cout << "Read: " << buffer;}// 文件自动关闭return 0; }
-
数组支持
#include <memory> #include <iostream>int main() {// C++11方式 std::unique_ptr<int[]> arr1(new int[5]);// C++14推荐方式 auto arr2 = std::make_unique<int[]>(5);for (int i = 0; i < 5; ++i) {arr1[i] = i * 2;arr2[i] = i * 3;}for (int i = 0; i < 5; ++i) {std::cout << arr1[i] << " " << arr2[i] << "\n";}// 数组自动释放return 0; }
-
将
this
转换为shared_ptr
#include <memory> #include <iostream>class MyClass : public std::enable_shared_from_this<MyClass> { public:std::shared_ptr<MyClass> getShared() {return shared_from_this();}~MyClass() {std::cout << "MyClass destroyed\n";} };int main() {auto ptr = std::make_shared<MyClass>();auto ptr2 = ptr->getShared();std::cout << "Reference count: " << ptr.use_count() << "\n";//Reference count: 2return 0; }
七、 选择智能指针的准则
-
使用
std::unique_ptr
:- 资源只有一个拥有者
- 需要高效的资源管理
- 需要将所有权转移到其他对象
-
使用
std::shared_ptr
:- 资源有多个拥有者
- 无法预先确定哪个拥有者最后使用资源
-
使用
std::weak_ptr
:- 需要观察共享资源但不参与生命周期管理
- 需要打破
std::shared_ptr
的循环引用
-
避免使用裸指针管理资源,优先使用智能指针。