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

C++-RAII

C++智能指针

1. 概念

RAII是Resource Acquisition Is Initialization的缩写,他是⼀种管理资源的类的设计思想,本质是⼀种利⽤对象⽣命周期来管理获取到的动态资源,避免资源泄漏,这⾥的资源可以是内存、⽂件指针、⽹络连接、互斥锁等等。RAII在获取资源时把资源委托给⼀个对象,接着控制对资源的访问,资源在对象的⽣命周期内始终保持有效,最后在对象析构的时候释放资源,这样保障了资源的正常释放,避免资源泄漏问题。

  • 资源获取在构造函数中完成。
  • 资源释放在析构函数中完成。
  • 智能指针类除了满⾜RAII的设计思路,还要⽅便资源的访问,所以智能指针类还会想迭代器类⼀样,重载 operator*/operator->/operator[] 等运算符,⽅便访问资源。

2. 标准库智能指针的使用

C++标准库中的智能指针都在<memory>头文件。

2.1 auto_ptr(已废弃)

2.1.1 概念和示例

auto_ptr是C++98时设计出来的智能指针,他的特点是拷⻉时把被拷⻉对象的资源的管理权转移给拷⻉对象,这是⼀个⾮常糟糕的设计,因为他会到被拷⻉对象悬空,访问报错的问题。

#include <iostream>
#include <memory> // 在 C++98/11 中// 示例:auto_ptr 的问题
void autoPtrDemo() {std::auto_ptr<int> p1(new int(10));std::auto_ptr<int> p2 = p1; // 所有权转移,p1 变为空// std::cout << *p1 << std::endl; // 运行时错误!std::cout << *p2 << std::endl; // 正常输出 10
}// 常见问题:意外所有权转移
void problematicFunction(std::auto_ptr<int> param) {// 函数调用时所有权已经转移
}void testAutoPtr() {std::auto_ptr<int> ptr(new int(20));problematicFunction(ptr); // ptr 现在为空// *ptr = 30; // 运行时错误!
}
2.1.2 模拟实现
#pragma oncenamespace simulate_auto_ptr {template <typename T>class auto_ptr {public:auto_ptr(T* _ptr) :ptr(_ptr) {}auto_ptr(auto_ptr<T>& self) {if (ptr) delete ptr;ptr = self.ptr;self.ptr = nullptr;}auto_ptr<T>& operator=(auto_ptr<T>& self) {if (this != &self) {if (ptr) delete ptr;ptr = self.ptr;self.ptr = nullptr;}return *this;}~auto_ptr() { if (ptr)delete ptr;}T& operator* () { return *ptr; }T* operator-> () { return ptr; }private:T* ptr;};
}

2.2 unique_ptr

2.2.1 概念和示例

unique_ptr是C++11设计出来的智能指针,特点是不支持拷贝,只支持移动,独占所有权的智能指针。

#include <iostream>
#include <memory>void uniquePtrDemo() {// 创建 unique_ptrstd::unique_ptr<int> ptr1 = std::make_unique<int>(42);std::unique_ptr<int[]> arr = std::make_unique<int[]>(5);// 访问数据std::cout << *ptr1 << std::endl; // 42arr[0] = 10;std::cout << arr[0] << std::endl; // 10// 移动语义 - 显式所有权转移std::unique_ptr<int> ptr2 = std::move(ptr1);// std::cout << *ptr1 << std::endl; // 错误!ptr1 为空std::cout << *ptr2 << std::endl; // 42// 释放所有权int* rawPtr = ptr2.release();delete rawPtr; // 需要手动释放// 重置指针auto ptr3 = std::make_unique<int>(100);ptr3.reset(new int(200)); // 自动释放旧内存,管理新内存ptr3.reset(); // 释放内存,ptr3 为空// 自定义删除器auto fileDeleter = [](FILE* f) {if (f) {std::cout << "关闭文件" << std::endl;fclose(f);}};std::unique_ptr<FILE, decltype(fileDeleter)> filePtr(fopen("test.txt", "w"), fileDeleter);
}
2.2.2 模拟实现
#pragma oncenamespace simulate_unique_ptr {// unique_ptr 不允许拷贝template <typename T>class unique_ptr {public:explicit unique_ptr(T* _ptr) :ptr(_ptr) {}unique_ptr(const unique_ptr<T>& self) = delete;unique_ptr<T>& operator=(const unique_ptr<T>& self) = delete;unique_ptr(unique_ptr<T>&& self) {ptr = self.ptr;self.ptr = nullptr;}~unique_ptr() { if (ptr)delete ptr;}T& operator* () { return *ptr; }T* operator-> () { return ptr; }private:T* ptr;};
}

2.3 shared_ptr

2.3.1 概念和示例

shared_ptr共享所有权,使用引用计数,支持拷贝和移动,当最后一个管理资源的shared_ptr被销毁时释放内存。

#include <iostream>
#include <memory>// 创建 shared_ptr
std::shared_ptr<int> ptr1 = std::make_shared<int>(42);
std::shared_ptr<int> ptr2 = ptr1; // 引用计数增加// 获取引用计数
std::cout << "引用计数: " << ptr1.use_count() << std::endl;
2.3.2 模拟实现
#pragma once
#include <iostream>
#include <functional>namespace simulate_shared_ptr {template<typename T>struct default_delete {void operator()(T* ptr) const { delete ptr; }};template<typename T>struct default_delete<T[]> {void operator()(T* ptr) const { delete[] ptr; }};// unique_ptr的实现方式 template<typename T , typename Deleter = default_delete<T>> class unique_ptr {};template<typename T>class shared_ptr {public:template<typename D>shared_ptr<T>(T* _ptr , D del) :ptr(_ptr) ,pcount(new int(1)) , deleter(del) {}// 构造的时候初始化引用计数shared_ptr<T>(T* _ptr) :ptr(_ptr) ,  pcount(new int(1)) {}shared_ptr<T>(const shared_ptr<T>& sp) :ptr(sp.ptr) , pcount(sp.pcount) { ++(*pcount); }shared_ptr<T>& operator=(const shared_ptr<T>& sp) {// 1.相同对象之间的赋值 p1 = p1;// 2.不同对象,但管理的资源相同之间的赋值 p1 = p2;// 3.不同对象,管理的资源也不同之间的赋值 p3 = p4;// 判断两个对象管理的不是同一个资源再相互赋值if (ptr != sp.ptr) {if (--(*pcount) == 0) {deleter(ptr);delete pcount;}ptr = sp.ptr;pcount = sp.pcount;++(*pcount);}return *this;}~shared_ptr<T>() {// 引用计数减到0释放空间if (--(*pcount) == 0) {delete ptr;delete pcount;}}T& operator*() { return *ptr; }T* operator->() { return ptr; }private:T* ptr;int* pcount;std::function<void(T*)> deleter = [](T* ptr){ delete ptr; };    //  默认使用delete};template<typename T>class shared_ptr<T[]> {public:T& operator[](size_t pos) { return ptr[pos]; }private:T* ptr;int* pcount;std::function<void(T*)> deleter;};
}

2.4 weak_ptr

std::weak_ptr 是 C++11 引入的一种智能指针,主要用于解决 std::shared_ptr 可能导致的循环引用问题,它本身不拥有对象的所有权。

循环引用问题:

#include <memory>
#include <iostream>
#include <string>class Node {
public:std::shared_ptr<Node> next;std::shared_ptr<Node> prev;Node(const std::string& name) : name(name) {std::cout << name << " 创建\n";}~Node() {std::cout << name << " 销毁\n";}std::string name;
};void circularReferenceProblem() {auto n1 = std::make_shared<Node>("Node1");auto n2 = std::make_shared<Node>("Node2");// 创建循环引用n1->next = n2;n2->prev = n1;std::cout << "n1 引用计数: " << n1.use_count() << std::endl; // 2std::cout << "n2 引用计数: " << n2.use_count() << std::endl; // 2// 退出作用域时,由于循环引用,对象不会被销毁!// 内存泄漏!
}

在这里插入图片描述

在这里插入图片描述


weak_ptr的主要特性:

  1. 不控制对象生命周期
    weak_ptr 指向一个由 shared_ptr 管理的对象,但不会增加其引用计数。当最后一个关联的 shared_ptr 被销毁时,无论是否有 weak_ptr 指向该对象,该资源都会被释放。

  2. 解决循环引用
    当两个对象相互持有 shared_ptr 时会形成循环引用,导致引用计数无法减为 0,内存泄漏。使用 weak_ptr 替代 shared_ptr 可打破循环。

  3. 需要转换为 shared_ptr 使用
    weak_ptr 不能直接访问对象,必须通过 lock() 方法返回⼀个管理资源的shared_ptr,如果资源已经被释放,返回 shared_ptr 是⼀个空对象,如果资源没有释放,则通过返回的 shared_ptr 访问资源是安全的。

  4. weak_ptr⽀持expired检查指向的资源是否过期

  5. 如果两个shared_ptr管理一个资源,再加一个 weak_ptr,当这两个shared_ptr都析构了,同时资源也会被释放,但是为了方便use_count的访问,资源释放了,但是引用计数不会释放,可能通过另一个引用计数来保证。

#include <memory>
#include <string>
using namespace std;void test() {std::shared_ptr<string> sp1(new string("111111"));std::shared_ptr<string> sp2(sp1);std::weak_ptr<string> wp = sp1;cout << wp.expired() << endl;   // 0cout << wp.use_count() << endl; // 2// sp1和sp2都指向了其他资源,则weak_ptr就过期了sp1 = std::make_shared<string>("222222");   cout << wp.expired() << endl;   // 0cout << wp.use_count() << endl; // 1sp2 = std::make_shared<string>("333333");cout << wp.expired() << endl;   // 1cout << wp.use_count() << endl; // 0wp = sp1;//std::shared_ptr<string> sp3 = wp.lock();auto sp3 = wp.lock();   cout << wp.expired() << endl;   // 0cout << wp.use_count() << endl; // 2
}

解决循环引用问题:

// 将 std::shared_ptr<Node> next;
//    std::shared_ptr<Node> prev; 
//  改为 weak_ptr<Node> next; weak_ptr<Node> prev;

2.5 shared_ptr和unique_ptr删除器的不同使用

  • unique_ptr 的删除器是模板参数的一部分,需要在声明时指定
  • shared_ptr 的删除器不是模板参数,而是在构造时指定
// unique_ptr 使用删除器
auto deleter = [](int* p) { delete p; 
};// 删除器是模板参数的一部分
std::unique_ptr<int, decltype(deleter)> uptr(new int, deleter);// shared_ptr 使用删除器
// 删除器在构造时指定,不影响类型
std::shared_ptr<int> sptr(new int, [](int* p) { delete p; 
});

2.6 底层怎么区分delete还是delete[]

智能指针通过模板特化删除器 来区分 deletedelete[]。标准库为数组类型提供了特化版本。

2.6.1 示例
#include <memory>
#include <iostream>// unique_ptr 的两种特化形式
void uniquePtrSpecialization() {// 1. 对于非数组类型 - 使用 deletestd::unique_ptr<int> singlePtr = std::make_unique<int>(42);// 2. 对于数组类型 - 使用 delete[]std::unique_ptr<int[]> arrayPtr = std::make_unique<int[]>(5);
}
2.6.2 部分实现
// 简化版的 unique_ptr 实现
namespace detail {// 默认删除器(非数组)template<typename T>struct default_delete {void operator()(T* ptr) const {delete ptr;}};// 数组特化的删除器template<typename T>struct default_delete<T[]> {void operator()(T* ptr) const {delete[] ptr;}};
}// 主模板
template<typename T, typename Deleter = detail::default_delete<T>>
class unique_ptr {
private:T* ptr;Deleter deleter;public:// 构造函数、析构函数等...~unique_ptr() {deleter(ptr);}
};// 数组特化
template<typename T, typename Deleter>
class unique_ptr<T[], Deleter> {
private:T* ptr;Deleter deleter;public:// 针对数组的特殊接口T& operator[](size_t index) {return ptr[index];}~unique_ptr() {deleter(ptr);}
};
  • 类模板部分特化的接口继承行为:在类的部分特化中,如果只提供特殊的接口,其他接口会自动使用主模板的版本。部分特化会继承主模板的所有接口。

  • 在部分特化中,不能重复主模板的默认参数。这是 C++ 模板语法的一个规定。

2.7 make_unique、make_shared与直接构造的区别

  • make_unique、make_shared异常安全。

  • make_shared性能更好。

    • 若直接构造,需要两次分配(容易产生内存碎片)

    • make_shared:管理的内存和引用计数分配在一起(减少内存碎片)

  • 若使用自定义删除器,必须使用直接构造。


文章转载自:

http://LBbvlsfZ.dbyLp.cn
http://K7DPr3UE.dbyLp.cn
http://FvxZt3fr.dbyLp.cn
http://vdmtwYSM.dbyLp.cn
http://YfUOUfPO.dbyLp.cn
http://MPCvmYJr.dbyLp.cn
http://G8nICeAQ.dbyLp.cn
http://7hY5rzKA.dbyLp.cn
http://xd9JAXZs.dbyLp.cn
http://fIRXgjnQ.dbyLp.cn
http://EkpHN6Sk.dbyLp.cn
http://gfP5s6kN.dbyLp.cn
http://jTJGA9Cj.dbyLp.cn
http://TF2NBRfI.dbyLp.cn
http://psV8xMqg.dbyLp.cn
http://udDfHw7S.dbyLp.cn
http://qHKLW9DH.dbyLp.cn
http://IKuH2eA3.dbyLp.cn
http://rMUjjIuu.dbyLp.cn
http://j1Ziy1BM.dbyLp.cn
http://h0n8I2p9.dbyLp.cn
http://50NPtQG0.dbyLp.cn
http://50OL7ILq.dbyLp.cn
http://ZQX8DvgQ.dbyLp.cn
http://EiAiAqMw.dbyLp.cn
http://Vj3cC7FJ.dbyLp.cn
http://IJumFP2s.dbyLp.cn
http://Hto1ez5G.dbyLp.cn
http://RQL8hD8F.dbyLp.cn
http://y8ClhYTT.dbyLp.cn
http://www.dtcms.com/a/373576.html

相关文章:

  • nginx反向代理,负载均衡,tomcat的数据流向图篇解析
  • 独立站SEO优化:如何应用移动代理IP提升关键词排名?
  • Linux初始——cgdb
  • 【T2I】Discriminative Probing and Tuning for Text-to-Image Generation
  • Vue: ref、reactive、shallowRef、shallowReactive
  • HarmonyOS 应用开发深度解析:基于 ArkTS 的跨组件状态管理最佳实践
  • 鸿蒙系统下的智能设备故障检测实战:从监控到自愈的全流程实现
  • windows11备份系统盘
  • 小迪web自用笔记31
  • 【前端埋点】纯前端实现 A/B Test
  • Vue3+Cesim ^1.122.0 Home按钮位置自定义;时间轴UTC时间转化为北京时间
  • 第五十五天(SQL注入增删改查HTTP头UAXFFRefererCookie无回显报错复盘)
  • leetcode 1317 将整数转换为两个无零整数的和
  • 高斯数据库(GaussDB)常用命令
  • git 配置本地添加ssh
  • ⸢ 肆 ⸥ ⤳ 默认安全建设方案:c-1.增量风险管控
  • 从零开始学大模型之大模型应用
  • 事务设置和消息分发
  • 人工智能-python-深度学习-神经网络-GoogLeNet
  • 告别进度拖延:19款项目进度管理软件深度测评
  • lesson56:CSS进阶指南:Flex布局、变换渐变与动画实战全解析
  • 【高等数学】第十一章 曲线积分与曲面积分——第四节 对面积的曲面积分
  • 精通Octokit:GitHub API开发全攻略
  • 超越模仿:探寻智能的本源
  • CSS 定位技术解析
  • IACheck赋能AI环评报告审核,推动环保设备制造行业发展
  • Photoshop保存图层
  • Java高级编程--XML
  • Nano Banana 技术深度解析:重新定义AI影像的革命性里程碑
  • 运作管理学习笔记5-生产和服务设施的选址