当前位置: 首页 > news >正文

C++11智能智能指针解析

        C++11 引入了 智能指针来解决手动管理动态内存的复杂性。它们能够自动管理堆内存,并在不再需要时自动释放,避免内存泄漏和悬空指针问题。C++11 提供了三种主要的智能指针类型:std::unique_ptrstd::shared_ptrstd::weak_ptr

1. std::unique_ptr:独占所有权

std::unique_ptr 是一种 独占所有权 的智能指针,它保证同一时间只能有一个智能指针拥有对象的所有权。这意味着对象只能被一个 unique_ptr 所管理,不能被多个指针共享。使用 std::unique_ptr 的场景通常是当一个对象的生命周期只需要在单个作用域内管理时。

特点
  • 独占所有权:不能复制,只能移动。
  • 生命周期:当 unique_ptr 离开其作用域时,会自动销毁并释放所管理的对象。
  • 轻量、高效:由于不涉及引用计数,unique_ptr 的效率通常比 shared_ptr 更高。
示例
#include <memory>
#include <iostream>struct MyClass {MyClass() { std::cout << "MyClass constructed\n"; }~MyClass() { std::cout << "MyClass destroyed\n"; }
};int main() {std::unique_ptr<MyClass> ptr1 = std::make_unique<MyClass>(); // 自动管理动态分配的 MyClass// std::unique_ptr<MyClass> ptr2 = ptr1; // 错误,不能复制 unique_ptrstd::unique_ptr<MyClass> ptr2 = std::move(ptr1); // 正确,通过移动所有权// ptr1 现在为空
}
重要操作
  • 创建智能指针:使用 std::make_unique<T>() 动态分配内存并返回 std::unique_ptr<T>,避免手动使用 new
  • 移动所有权:使用 std::move 进行所有权转移,不能复制。

2. std::shared_ptr:共享所有权

std::shared_ptr 实现了 共享所有权,即可以有多个智能指针共同拥有同一个对象。对象的生命周期会被多个 shared_ptr 共享,只有当最后一个 shared_ptr 被销毁时,托管的对象才会被释放。

特点
  • 共享所有权:多个 shared_ptr 可以指向同一个对象。
  • 引用计数shared_ptr 维护一个引用计数器,当指向对象的所有 shared_ptr 被销毁时,引用计数器变为 0,对象会自动释放。
  • 适用场景:多个对象需要共享同一个资源时,例如在复杂的资源共享或对象关系管理中使用。
示例
#include <memory>
#include <iostream>struct MyClass {MyClass() { std::cout << "MyClass constructed\n"; }~MyClass() { std::cout << "MyClass destroyed\n"; }
};int main() {std::shared_ptr<MyClass> ptr1 = std::make_shared<MyClass>();{std::shared_ptr<MyClass> ptr2 = ptr1; // 引用计数增加std::cout << "Reference count: " << ptr1.use_count() << "\n"; // 输出 2}// 离开作用域,ptr2 被销毁,引用计数减少为 1std::cout << "Reference count: " << ptr1.use_count() << "\n"; // 输出 1
}
重要操作
  • 创建智能指针:使用 std::make_shared<T>() 创建共享指针,比手动分配内存效率更高。
  • 引用计数:使用 use_count() 查看当前引用计数。
注意事项
  • 循环引用问题:如果两个或多个 shared_ptr 对象形成循环引用(即 A 指向 B,B 又指向 A),那么即使引用计数变为 0,内存也不会被释放,导致内存泄漏。为了解决这个问题,引入了 std::weak_ptr

3. std::weak_ptr:弱引用

std::weak_ptr 是为了防止 std::shared_ptr 之间的 循环引用问题 而设计的。weak_ptr 并不影响引用计数,不拥有对象的所有权。它只是一个观察者,可以访问由 shared_ptr 管理的对象。如果 shared_ptr 管理的对象已经被释放,那么 weak_ptr 变为无效。

特点
  • 不增加引用计数weak_ptr 只是持有对象的引用,不会影响 shared_ptr 的引用计数。
  • 生命周期观察:使用 weak_ptr 观察对象的生命周期,可以通过 lock() 函数将 weak_ptr 转换为 shared_ptr,安全地访问对象。
示例
#include <memory>
#include <iostream>struct MyClass {MyClass() { std::cout << "MyClass constructed\n"; }~MyClass() { std::cout << "MyClass destroyed\n"; }
};int main() {std::shared_ptr<MyClass> sptr = std::make_shared<MyClass>();std::weak_ptr<MyClass> wptr = sptr; // weak_ptr 引用 sptr,但不增加引用计数std::cout << "Reference count: " << sptr.use_count() << "\n"; // 输出 1if (auto shared = wptr.lock()) {std::cout << "Object is still alive\n";} else {std::cout << "Object has been destroyed\n";}sptr.reset(); // 释放 shared_ptr 管理的对象if (auto shared = wptr.lock()) {std::cout << "Object is still alive\n";} else {std::cout << "Object has been destroyed\n";}
}

shared_ptr 内部维护的关键成员变量:

1. 指向对象的原始指针(Managed Object Pointer)

  • 这是指向实际动态分配对象的原始指针。它是 shared_ptr 管理的对象,shared_ptr 使用这个指针来访问对象。
  • 例如:T* ptr,它指向动态分配的对象(例如通过 make_sharednew 分配的对象)。

2. 控制块(Control Block)

  • 控制块shared_ptr 背后维护的一个数据结构,包含了引用计数、弱引用计数以及其他额外的信息。控制块并不与所管理的对象共享地址,它是 shared_ptr 机制中的核心,包含多个关键数据:
    • 引用计数(Reference Counter)
      • 一个用于记录有多少个 shared_ptr 实例同时引用该对象的计数器,称为共享引用计数。当创建一个新的 shared_ptr 实例指向相同对象时,该计数器递增;当 shared_ptr 被销毁或重置时,该计数器递减。
      • 当引用计数降为 0 时,托管的对象会被销毁。
    • 弱引用计数(Weak Reference Counter)
      • 另一个计数器,记录有多少个 weak_ptr 实例引用该对象。weak_ptr 不会影响共享引用计数,但会影响控制块的生命周期。
      • 当共享引用计数和弱引用计数都降为 0 时,控制块本身也会被销毁。
    • 自定义删除器(Custom Deleter)
      • shared_ptr 可以绑定一个自定义删除器,用于控制对象的销毁方式。如果你不指定删除器,shared_ptr 会使用默认的 delete 运算符释放对象。
      • 删除器保存在控制块中,当最后一个 shared_ptr 离开作用域时,它会调用这个删除器来销毁对象。

3. 弱引用管理(Weak Pointer Management)

  • shared_ptrweak_ptr 的结合使用涉及到对控制块中弱引用计数的维护。weak_ptr 不会增加对对象的共享引用计数,但会使控制块的弱引用计数增加。这样,即使没有任何 shared_ptr 实例指向对象,控制块依然存在,直到所有 weak_ptr 也被销毁。

具体控制块结构的解释

可以将控制块理解为包含以下信息的结构体(这是一个概念上的简化):

struct ControlBlock {int shared_count;   // 共享引用计数int weak_count;     // 弱引用计数void(*deleter)(T*); // 自定义删除器函数指针
};

每次创建 shared_ptr 时,shared_count 会增加,而 weak_count 仅在创建 weak_ptr 时增加。

控制块与对象的关系

  • 独立于对象的存储:控制块是一个独立的数据结构,和对象的内存分配是分开的。当使用 make_shared 时,对象和控制块可能会被分配到同一块内存区域中,但它们的作用是不同的。
  • 生命周期管理:当 shared_count 变为 0 时,shared_ptr 管理的对象会被销毁;当 weak_count 也为 0 时,控制块才会被销毁。

shared_ptr 循环引用的例子

#include <iostream>
#include <memory>struct B;struct A {std::shared_ptr<B> ptrB;  // A持有B的shared_ptr~A() { std::cout << "A destroyed\n"; }
};struct B {std::shared_ptr<A> ptrA;  // B持有A的shared_ptr~B() { std::cout << "B destroyed\n"; }
};int main() {std::shared_ptr<A> a = std::make_shared<A>();std::shared_ptr<B> b = std::make_shared<B>();a->ptrB = b;  // A拥有Bb->ptrA = a;  // B拥有A// 循环引用导致A和B永远不会被销毁return 0;
}

在这个例子中:

  • A 持有一个指向 Bshared_ptr,而 B 也持有一个指向 Ashared_ptr
  • 由于 shared_ptr 会增加引用计数,ab 的引用计数永远无法减为 0,导致它们的析构函数不会被调用,从而造成内存泄漏。

解决循环引用:使用 std::weak_ptr

要解决循环引用问题,可以将其中一个 shared_ptr 替换为 std::weak_ptrweak_ptr 不会影响引用计数,因此可以打破循环引用。

解决后的代码

#include <iostream>
#include <memory>struct B;struct A {std::shared_ptr<B> ptrB;  // A持有B的shared_ptr~A() { std::cout << "A destroyed\n"; }
};struct B {std::weak_ptr<A> ptrA;    // B持有A的weak_ptr,打破循环引用~B() { std::cout << "B destroyed\n"; }
};int main() {std::shared_ptr<A> a = std::make_shared<A>();std::shared_ptr<B> b = std::make_shared<B>();a->ptrB = b;  // A拥有Bb->ptrA = a;  // B弱引用A,不增加引用计数// 正常销毁A和B,输出"A destroyed"和"B destroyed"return 0;
}

关键点

  • BA 的引用改为 std::weak_ptrweak_ptr 不增加引用计数,不会阻止对象的销毁。
  • 在需要使用 weak_ptr 指向的对象时,可以调用 lock() 方法将其转换为 shared_ptr,如果对象已被销毁,则返回 nullptr


http://www.mrgr.cn/news/40588.html

相关文章:

  • 8G 显存玩转书生大模型 Demo
  • 原来还有【快速排序】 qsort() 函数
  • 迪杰斯特拉算法 Dijkstra‘s Algorithm 详解
  • 音频内容创作难吗?5分钟了解NotebookLM自动生成播客:让内容创作变得如此简单
  • kubeadm部署k8s集群,版本1.23.6;并设置calico网络BGP模式通信,版本v3.25--未完待续
  • 【数据结构与算法】时间复杂度和空间复杂度例题
  • 【C语言指南】数据类型详解(下)——自定义类型
  • 【JavaEE】——多线程常用类
  • 你的虚拟猫娘女友,快来领取!--文心智能体平台
  • 将onnx模型中的类别信息导出到文本
  • JAVA认识异常
  • 数值计算的程序设计问题举例
  • 51单片机的智能家居【proteus仿真+程序+报告+原理图+演示视频】
  • 排水系统C++
  • 第5篇:勒索病毒自救指南----应急响应篇
  • 构建现代化社区医疗服务:SpringBoot平台
  • 【JavaEE】http/https 超级详解
  • 使用Materialize制作unity的贴图,Materialize的简单教程,Materialize学习日志
  • Raspberry Pi3B+之Rpanion(gst)和ffmpeg验证
  • 从零开始,5个步骤轻松掌握越来越火的JAX深度学习框架