c++总结-04-智能指针

[复制链接]
发表于 2025-7-30 19:23:39 | 显示全部楼层 |阅读模式
一、内存管理的一些基础

new/delete 表达式

new 表达式完成两件事情:
• 1. 分配对象所必要的内存
• 2. 调用构造器构造对象初始状态
delete 表达式完成两件事情:
• 1. 调用析构器析构对象状态
• 2. 开释对象所占的内存
new[] /delete[] 表达式

new[] 表达式完成两件事情:
• 1. 分配对象数组所必要的内存
• 2. 每一个元素调用构造器构造对象初始状态
delete[] 表达式完成两件事情
• 1. 每一个元素调用析构器析构对象状态
• 2. 开释对象数组所占的全部内存
new[]/delete[] 不能和new/delete 混搭,必须匹配
placement new

在指定内存位置构造对象
  1. void* memory = std::malloc(sizeof(MyClass));
  2. MyClass* myObject = ::new (memory) MyClass("Software");
复制代码
• 只负责构造对象,不负责分配内存
• 没有placement delete,直接显式调用析构器即可。
  1. myObject->~MyClass();
  2. std::free(memory);
复制代码
new/delete 操纵符

• operator new负责分配内存(当new表达式被调用时)
• 可以定义全局也可以定义针对某一个类的“成对重载”
  1. auto operator new(size_t size) -> void* {
  2. void* p = std::malloc(size);
  3. std::cout << "allocated " << size << " byte(s)\n";
  4. return p;
  5. }
  6. auto operator delete(void* p) noexcept -> void {
  7. std::cout << "deleted memory\n";
  8. return std::free(p);
  9. }
复制代码
new[]/delete[] 操纵符

• new/delete 操纵符对应的数组形式, 可以成对重载
  1. auto operator new[](size_t size) -> void* {
  2. void* p = std::malloc(size);
  3. std::cout << "allocated " << size << " byte(s) new[]\n"; return p;
  4. }
  5. auto operator delete[](void* p) noexcept -> void {
  6. std::cout << "deleted memory delete[]\n";
  7. return std::free(p);
  8. }
复制代码
为某一个类重载new/delete操纵符
  1. class MyClass {
  2. public:
  3. auto operator new(size_t size) -> void* {
  4. return ::operator new(size);
  5. }
  6. auto operator delete(void* p) -> void {
  7. ::operator delete(p);
  8. }
  9. };
  10. MyClass* p = ::new MyClass{};
  11. ::delete p;
复制代码
小对象优化

• 堆分配大概有严峻的碎片效应
• 不是全部的new都一定存储在堆上,可以自定义• 栈适合存储连续的少量对象
• 堆适合存储离散的大量对象
• 利用栈作为对象缓冲区
  1. class MiniString {
  2. private:
  3.     // 内部存储:联合体区分堆模式/栈模式
  4.     union {
  5.         char* heap_ptr;     // 堆模式:指向动态内存
  6.         char stack_buf[16]; // 栈模式:存储最多15字符+1结束符
  7.     };
  8.     // 高1位标记模式,低15位记录长度(仅示例,实际需位操作)
  9.     size_t metadata;
  10.     // 辅助函数
  11.     bool is_heap() const { return metadata & 0x8000; } // 最高位为1表示堆模式
  12.     size_t length() const { return metadata & 0x7FFF; } // 低15位为长度
  13. public:
  14.     explicit MiniString(const char* str) {
  15.         size_t len = strlen(str);
  16.         if (len < 16) {
  17.             // 栈模式:直接复制到内部缓冲区
  18.             strcpy(stack_buf, str);
  19.             metadata = len; // 最高位0表示栈模式
  20.         }
  21.         else {
  22.             // 堆模式:动态分配内存
  23.             heap_ptr = new char[len + 1];
  24.             strcpy(heap_ptr, str);
  25.             metadata = len | 0x8000; // 最高位置1
  26.         }
  27.     }
  28.     // 析构函数
  29.     ~MiniString() {
  30.         if (is_heap()) {
  31.             delete[] heap_ptr;
  32.         }
  33.         // 栈模式无需额外操作
  34.     }
  35.     // 打印字符串
  36.     void print() const {
  37.         if (is_heap()) {
  38.             std::cout << heap_ptr;
  39.         }
  40.         else {
  41.             std::cout << stack_buf;
  42.         }
  43.     }
  44. };
  45. int main() {
  46.     // 短字符串
  47.     MiniString s1("hello\n");
  48.     s1.print(); // 输出 "hello"(存储在栈缓冲区)
  49.     // 长字符串
  50.     MiniString s2("this_is_a_very_long_string_that_exceeds_15_characters\n");
  51.     s2.print(); // 输出长字符串(存储在堆)
  52.     return 0;
  53. }
复制代码
结果:

二、智能指针

• 智能指针封装了裸指针,内部照旧裸指针的调用
• 智能指针利用RAII特点,将对象生命周期利用栈来管理。
• 智能指针区分了全部权,因此利用责任更为清晰。
• 智能指针大量利用操纵符重载和函数内联特点,调用本钱和裸指针无差别
一、unique_ptr解析

• 默认情况存储本钱和裸指针相同,无添加
• 独占拥有权
• 不支持拷贝构造,只支持移动(全部权转移)
• 可以转换成shared_ptr
• 可自定义删除操纵(policy设计),留意不同删除操纵的存储本钱:
         • 函数对象(实例成员决定巨细)
         • lambda (留意捕获效应会导致lambda对象变大)
         • 函数指针(增加一个指针长度)
二、unique指针内存模子


三、基本利用方法

1. 创建 unique_ptr
  1. // 方式1:直接构造
  2. std::unique_ptr<int> ptr1(new int(42));
  3. // 方式2:推荐使用 make_unique (C++14起)
  4. std::unique_ptr<int> ptr2 = std::make_unique<int>(42);
  5. // 方式3:创建数组
  6. std::unique_ptr<int[]> arr = std::make_unique<int[]>(10);
复制代码
2. 全部权转移
  1. std::unique_ptr<int> ptrA = std::make_unique<int>(10);
  2. std::unique_ptr<int> ptrB = std::move(ptrA); // 所有权转移
  3. // 此时 ptrA == nullptr,ptrB 拥有资源
复制代码
3. 开释资源
  1. ptrB.reset();       // 显式释放资源
  2. ptrB.reset(new int(20)); // 释放旧资源,接管新资源
复制代码
四、关键留意事项

禁止拷贝:unique_ptr 是独占全部权的智能指针,不能拷贝构造或拷贝赋值
  1. std::unique_ptr<int> a = std::make_unique<int>(1);
  2. std::unique_ptr<int> b = a; // 错误!编译失败
复制代码
多态利用必须虚析构:
  1. class Base {
  2. public:
  3.     virtual ~Base() = default; // 必须要有虚析构函数
  4. };
复制代码
数组与对象形式不同:
  1. // 对象版本
  2. std::unique_ptr<MyClass> obj;
  3. // 数组版本
  4. std::unique_ptr<MyClass[]> arr;
复制代码
避免裸指针转换:
  1. int* raw = ptr.get(); // 可以获取但不建议长期保存
  2. delete raw; // 严重错误!会导致双重释放
复制代码
五、unique_ptr利用场景

• 为动态分配内存提供异常安全(RAII)
• 将动态分配内存的全部权传递给函数
• 从函数内返回动态分配的内存(工厂函数)
• 在容器中生存指针
  1. std::unique_ptr<Shape> createShape(ShapeType type) {
  2.     switch(type) {
  3.         case Circle: return std::make_unique<Circle>();
  4.         case Square: return std::make_unique<Square>();
  5.     }
  6. }
复制代码
• 在对象中生存多态子对象(数据成员)
六、高级用法


  • 1.自定义删除器
  1. // 函数指针形式
  2. void FileDeleter(FILE* fp) { fclose(fp); }
  3. std::unique_ptr<FILE, decltype(&FileDeleter)> filePtr(fopen("a.txt", "r"), FileDeleter);
复制代码
  1. // 函数对象形式
  2. struct ArrayDeleter {
  3.     void operator()(int* p) { delete[] p; }
  4. };
  5. std::unique_ptr<int, ArrayDeleter> arrPtr(new int[10]);
复制代码

  • 2.多态转换
  1. class Base { virtual ~Base() {} };
  2. class Derived : public Base {};
  3. std::unique_ptr<Derived> derived = std::make_unique<Derived>();
  4. std::unique_ptr<Base> base = std::move(derived); // 向上转型
复制代码

  • 3.作为函数参数
  1. // 1. 值传递(转移所有权)
  2. void takeOwnership(std::unique_ptr<Resource> res);
  3. // 2. 引用传递(不转移所有权)
  4. void useResource(const std::unique_ptr<Resource>& res);
  5. // 3. 原始指针访问(不取得所有权)
  6. void workWithResource(Resource* res);
复制代码
unique_ptr可转为shared_ptr
  1. std::unique_ptr<std::string> foo()
  2. {
  3.     return std::make_unique<std::string>("foo");
  4. }
  5. int main()
  6. {
  7.    
  8.     std::shared_ptr<std::string> sp1 = foo();
  9.     auto up = std::make_unique<std::string>("Hello World");
  10.     std::shared_ptr<std::string> sp2 = std::move(up);
  11.     //std::shared_ptr<std::string> sp3 = up;
  12.     if(sp2.unique())
  13.         cout<<"only 1 count"<<endl;
  14. }
复制代码
七、shared_ptr解析

• 共享全部权
• 存储本钱较裸指针多了引用计数指针(和相关控制块-共享)• 接口慎用(蔓延问题)
• 线程安全,引用计数增减会减慢多核性能
• 最适合共享的稳定命据
• 支持拷贝构造,支持移动
八、共享指针内存模子


九、利用方法

1.基本利用
  1. #include <memory>
  2. // 创建 shared_ptr
  3. std::shared_ptr<int> p1 = std::make_shared<int>(42); // 推荐方式
  4. std::shared_ptr<int> p2(new int(100));               // 直接构造(不推荐)
  5. // 拷贝和赋值(引用计数递增)
  6. std::shared_ptr<int> p3 = p1;  // p1、p3 共享所有权
  7. // 解引用访问对象
  8. std::cout << *p1 << std::endl; // 输出 42
复制代码
2.自定义删除器
  1. // 管理文件句柄
  2. std::shared_ptr<FILE> file_ptr(
  3.     fopen("test.txt", "r"),
  4.     [](FILE* f) { if (f) fclose(f); } // 自定义删除器
  5. );
  6. // 管理数组(需自定义删除器)
  7. std::shared_ptr<int[]> arr_ptr(
  8.     new int[10],
  9.     [](int* p) { delete[] p; }
  10. );
复制代码
3.结合 weak_ptr 冲破循环引用
  1. class B;
  2. class A {
  3. public:
  4.     std::shared_ptr<B> b_ptr;
  5. };
  6. class B {
  7. public:
  8.     std::weak_ptr<A> a_weak_ptr; // 使用 weak_ptr 避免循环引用
  9. };
  10. auto a = std::make_shared<A>();
  11. auto b = std::make_shared<B>();
  12. a->b_ptr = b;
  13. b->a_weak_ptr = a; // 不会增加引用计数
复制代码
十、实用场景

1.共享资源全部权
多个对象必要共享同一块动态分配的内存。
2.复杂生命周期管理
不确定何时开释资源时(如异步操纵、缓存体系)。
3.容器的对象管理
容器中存储动态分配的对象,避免手动内存管理。
4.与第三方库交互
管理 C 风格 API 分配的资源(如文件句柄、网络连接)。
十一、实现原理

1.控制块(Control Block)
• 构成:引用计数、弱引用计数、删除器(Deleter)、分配器(Allocator)。
• 内存布局:
         std::make_shared:对象和控制块连续分配(高效,淘汰内存碎片)。
         直接构造:对象和控制块分开分配(两次内存申请)。
2.引用计数
• 线程安全:引用计数的增减是原子的(通过 std::atomic 实现),但对象自己的访问必要额外同步。
• 计数规则:
         构造或拷贝 shared_ptr 时,引用计数 +1。
         析构或赋值时,引用计数 -1,归零时调用删除器开释资源。
3.weak_ptr 的作用
• 不增加引用计数,但可检测资源是否有效(通过 lock() 获取临时 shared_ptr)。
• 弱引用计数归零时,开释控制块。
十二、留意点

1.避免常见错误
         • 不要用裸指针初始化多个 shared_ptr:
  1.         int* raw = new int(10);
  2.         std::shared_ptr<int> p1(raw);
  3.         std::shared_ptr<int> p2(raw); // 错误!会导致双重释放
复制代码
         • 不要用栈对象地址初始化:
  1.         int x = 10;
  2.         std::shared_ptr<int> p(&x); // 错误!栈对象会自动释放
复制代码
2.性能与开销
         • 原子操纵开销:引用计数的原子操纵有稍微性能损耗(高并发场景需评估)。
         • 内存占用:每个 shared_ptr 携带指向控制块的指针(通常为 16 字节)。
3.循环引用
         •问题:两个对象互相持有对方的 shared_ptr,引用计数永不归零。
         •解决:用 weak_ptr 替代其中一个指针。
4. 优先利用 make_shared
         •优点:
                内存分配优化(对象和控制块连续)。
                避免因异常导致的内存泄漏(如构造函数抛出异常时,new 分配的资源大概泄漏)。
         •例外:需自定义删除器或需单独分配对象时
十三、智能指针最佳实践

• 智能指针仅用于管理内存,不要用于管理非内存资源。非内存资源利用RAII类封装
• 用 unique_ptr表达唯一全部权
• 用 shared_ptr表达共享全部权
• 优先采用 unique_ptr 而不是 shared_ptr,除非必要共享全部权
• 针对共享情况考虑利用引用计数。
• 利用 make_unique() 创建 unique_ptr
• 利用 make_shared() 创建 shared_ptr
• 利用 weak_ptr 防止 shared_ptr 的循环引用

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
继续阅读请点击广告

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
回复

使用道具 举报

×
登录参与点评抽奖,加入IT实名职场社区
去登录
快速回复 返回顶部 返回列表