C++八股——智能指针

news2025/12/17 10:57:51

文章目录

    • 1. 背景
    • 2. 原理与使用
      • 2.1 auto_ptr
      • 2.2 unique_ptr
      • 2.3 shared_ptr
      • 2.4 weak_ptr
      • 2.5 定制删除器

1. 背景

智能指针不是指针,是一个管理指针的类,用来存储指向动态分配对象的指针,负责自动释放动态分配的对象,防止堆内存泄漏悬空指针等问题。

动态分配的资源,交给一个类对象去管理,当类对象声明周期结束时,自动调用析构函数释放资源。

  • C++ 98中产生第一个智能指针auto_ptr
  • C++ 11起提供三个主要的智能指针,位于<memory>头文件中:
    • std::unique_ptr
    • std::shared_ptr
    • std::weak_ptr

关键特性对比

类型所有权复制语义线程安全性能开销
unique_ptr独占移动转移单线程安全
shared_ptr共享允许复制引用计数原子化较高
weak_ptr无所有权允许复制依赖关联shared

2. 原理与使用

智能指针的基本原理是利用RAII,在对象构造时获取资源,接着控制对资源的访问使之在对象的生命周期内始终保持有效,最后在 对象析构的时候释放资源(不需要显式地释放资源)。

如果只是简单地用类封装指针,如:

template<class T>
class Smartptr
{
public:
    Smartptr(T* ptr) : _ptr(ptr) {}
    ~Smartptr() {
        delete _ptr;
    }
    
    T& operator*() {
        return *_ptr;
    }
    
    T* operator->(){
        return _ptr;
    }
private:
    T* _ptr;
};

会出现拷贝问题:用一个智能指针赋值给另一个指针指针时,因为是浅拷贝,会将两个指针指向同一块内存,在程序结束析构智能指针时释放两次空间,导致程序崩溃。

为了解决这个问题,出现以下四类智能指针:

2.1 auto_ptr

原理

管理权转移,被拷贝对象把资源管理权转移给拷贝对象,导致被拷贝对象悬空。保证了一个资源只有一个对象对其进行管理,这时候一个资源就不会被多个释放。

使用

auto_ptr<int> ap1(new int(1));
auto_ptr<int> ap2(ap1);
// 此时ap1悬空

模拟实现

template<class T>
class auto_ptr 
{
public:
    auto_ptr(T* ptr) : _ptr(ptr) {}
    
    auto_ptr(const auto_ptr<T>& ap) {
        _ptr = ap._ptr;
        ap._ptr = nullptr;
    }
    
    auto_ptr<T>& operator=(const auto_ptr<T>& ap) {
        // 检测是否自己给自己赋值
        if(this != &ap) {
            // 释放当前资源
            if(_ptr) delete _ptr;
            _ptr = ap._ptr;
            ap._ptr = nullptr;
        }
        return *this;
    }
    
    ~auto_ptr() {
        delete _ptr;
    }
    
    T& operator*() {
        return *_ptr;
    }
    
    T* operator->() {
        return _ptr;
    }
private:
    T* _ptr;
};

2.2 unique_ptr

原理

通过删除拷贝构造函数/赋值运算符来防止拷贝

使用

unique_ptr<int> up0 = make_unique<int>(0);
unique_ptr<int> up1(new int(1));
unique_ptr<int> up2(new int(2));
sp1 = sp2; // 报错

在函数返回unique_ptr时不要返回其引用:

auto getUnique() {
    auto ptr = std::make_unique<int>(10);
    return ptr;  // 正确:移动语义转移所有权
}

// auto& getUniqueRef() { ... }  // 错误:返回引用会导致悬空指针

模拟实现

template<class T>
class unique_ptr 
{
public:
    unique_ptr(T* ptr) : _ptr(ptr) {}
    
    unique_ptr(const unique_ptr<T>& up) = delete;
    unique_ptr<T>& operator=(const unique_ptr<T>& ap) = delete;
    
    ~unique_ptr() {
        delete _ptr;
    }
    
    T& operator*() {
        return *_ptr;
    }
    
    T* operator->() {
        return _ptr;
    }
private:
    T* _ptr;
};

2.3 shared_ptr

原理

通过引用计数的方式解决智能指针的拷贝问题:

  • shared_ptr给每个资源都维护了着一份计数用来记录该份资源被几个对象共享;
  • 在对象被销毁时(也就是析构函数调用),引用计数减一;
  • 如果引用计数是0,就说明自己是最后一个使用该资源的对象,就可以释放该资源;
  • 如果引用计数不是0,就说明还有其他对象在使用该份资源,不能释放该资源。

使用

shared_ptr<int> sp1(new int(1));
cout << sp1.use_count() << endl; // 1
shared_ptr<int> sp2(sp1);
cout << sp2.use_count() << endl; // 2

注意避免混用裸指针与智能指针

int* raw = new int(5);
std::shared_ptr<int> p1(raw);
std::shared_ptr<int> p2(raw);  // 双重释放!

模拟实现

template<class T>
class shared_ptr
{
public:
    shared_ptr(T* ptr) : _ptr(ptr), _pcount(new int(1)) {}
   
    ~shared_ptr(){
        Release();
    }

    shared_ptr(const shared_ptr<T>& sp){
        _ptr = sp._ptr;
        _pcount = sp._pcount;  
        ++(*_pcount);
    }

    void Release() {
        if (--(*_pcount) == 0) {
            delete _pcount;
            delete _ptr;
        }
    }

    shared_ptr<T>& operator=(const shared_ptr<T>& sp) {
        //资源地址不一样
        if (_ptr != sp._ptr) {
            Release();
            _pcount = sp._pcount;
            _ptr = sp._ptr;
            ++(*_pcount);
        }

        return *this;
    }

    int use_count() {
        return *_pcount;
    }

    // 像指针一样
    T& operator*() {
        return *_ptr;
    }

    T* operator->() {
        return _ptr;
    }

    T& operator[](size_t pos) {
        return _ptr[pos];
    }
private:
    T* _ptr;
    int* _pcount;
};

为什么引用计数要用指针,而不用成员变量或者静态成员变量?

  1. 若将引用计数作为普通成员变量:不同 shared_ptr 副本之间无法共享计数(成员变量属于对象,而非资源)。

  2. 静态变量(static)的特性:

    • 属于类本身,所有对象共享同一个静态变量。
    • 生命周期与程序一致,无法动态创建或销毁。
    • 全局唯一性,无法为每个资源单独维护计数。

    这与智能指针的资源独立性要求直接冲突:每个 shared_ptr 需要为其管理的资源单独维护引用计数,而静态变量会导致所有资源共享同一个计数器,引发严重错误。

    如果使用静态变量来计数,以下代码会出现错误:

    int main() {
        int* a = new int(10);
        int* b = new int(20);
        
        BadSharedPtr p1(a);  // count=1
        BadSharedPtr p2(b);  // count=2(错误!两个资源共享同一个计数器)
        
        p1.~BadSharedPtr();  // count=1(但 a 未被删除)
        p2.~BadSharedPtr();  // count=0,错误地删除 b,而 a 泄漏!
        return 0;
    }
    // 结果:a 内存泄漏,b 被提前释放,且程序崩溃。
    

循环引用问题

class Node;

class Parent {
public:
    std::shared_ptr<Node> child;  // Parent 持有 Node 的 shared_ptr
    ~Parent() { std::cout << "Parent destroyed\n"; }
};

class Node {
public:
    std::shared_ptr<Parent> parent;  // Node 也持有 Parent 的 shared_ptr(循环引用)
    ~Node() { std::cout << "Node destroyed\n"; }
};

int main() {
    auto parent = std::make_shared<Parent>();  // parent 引用计数 = 1
    auto node = std::make_shared<Node>();      // node 引用计数 = 1

    parent->child = node;    // node 引用计数 +1 → 2
    node->parent = parent;   // parent 引用计数 +1 → 2

    return 0;
    // main 结束时:
    // parent 引用计数 -1 → 1 → Parent 未被销毁!
    // node 引用计数 -1 → 1 → Node 未被销毁!
}

2.4 weak_ptr

原理

观察但不拥有资源,用于解决shared_ptr循环引用问题。

使用

  • 解决shared_ptr循环引用问题:

    #include <memory>
    
    class Node;  // 前置声明
    
    class Parent {
    public:
        std::shared_ptr<Node> child;
        ~Parent() { std::cout << "Parent destroyed\n"; }
    };
    
    class Node {
    public:
        // std::shared_ptr<Parent> parent;  // 循环引用导致内存泄漏
        std::weak_ptr<Parent> parent;      // 改用 weak_ptr 解决
        ~Node() { std::cout << "Node destroyed\n"; }
    };
    
    int main() {
        auto parent = std::make_shared<Parent>(); // parent 引用计数 = 1
        auto node = std::make_shared<Node>();     // node 引用计数 = 1
        
        parent->child = node;   // node 引用计数 +1 → 2
        node->parent = parent;  // weak_ptr 不会增加引用计数
        
        return 0;  // 正确析构
        // main 结束时:
        // parent 引用计数 -1 → 0 → Parent 销毁 → child 销毁 → node 引用计数 -1  → 1
        // node 引用计数 -1 → 0 → Node 销毁!
    }
    
  • 安全访问共享资源:

    #include <memory>
    #include <iostream>
    
    class Data {
    public:
        void process() { std::cout << "Processing data...\n"; }
    };
    
    int main() {
        std::shared_ptr<Data> sharedData = std::make_shared<Data>();
        std::weak_ptr<Data> weakData = sharedData;
    
        // 检查 weak_ptr 是否有效
        if (auto tmp = weakData.lock()) {  // 提升为 shared_ptr
            tmp->process();  // 安全使用
            std::cout << "Use count: " << tmp.use_count() << "\n";  // 输出 2
        } else {
            std::cout << "Data expired\n";
        }
    
        sharedData.reset();  // 释放资源
    
        if (weakData.expired()) {
            std::cout << "Data is no longer available\n";
        }
        return 0;
    }
    

模拟实现

template<class T>
class weak_ptr
{
public:
	weak_ptr() :_ptr(nullptr) {}
	weak_ptr(const shared_ptr<T>& wp) {
		_ptr = wp.get();
	}
 
	weak_ptr<T>& operator=(const shared_ptr<T>& wp) {
		_ptr = wp.get();
		return *this;
	}
 
	T& operator*() {
		return *_ptr;
	}
 
	T* operator->() {
		return _ptr;
	}
private:
	T* _ptr;
};

2.5 定制删除器

定制删除器可以解决:如何正确释放用new或者new []开辟的资源。

template<class U, class D> unique_ptr(U* p, D del);

其中Del参数是一个定制删除器,是一个可调用对象,比如函数指针、仿函数、lambda表达式以及被包装器包装后的可调用对象。

// unique_ptr 自定义删除器
auto del = [](int* p) { delete[] p; };
std::unique_ptr<int[], decltype(del)> arr(new int[10], del);

参考

  • C++智能指针详解-CSDN博客
  • DeepSeek

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2375553.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

「华为」人形机器人赛道投资首秀!

温馨提示&#xff1a;运营团队2025年最新原创报告&#xff08;共210页&#xff09; —— 正文&#xff1a; 近日&#xff0c;【华为】完成具身智能赛道投资首秀&#xff0c;继续加码人形机器人赛道布局。 2025年3月31日&#xff0c;具身智能机器人头部创企【千寻智能&#x…

格雷希尔G10和G15系列自动化快速密封连接器,适用于哪些管件的密封,以及它们相关的特性有哪些?

格雷希尔G10和G15系列快速密封连接器&#xff0c;用于自动化和半自动化过程中的外部或内部密封&#xff0c;通过使用气压驱动来挤压内部的密封圈&#xff0c;创造一个适用于各种管件的无泄漏密封连接&#xff0c;连接器内部的弹性密封圈可以提供其他产品不能提供的卓越密封性能…

专栏特辑丨悬镜浅谈开源风险治理之SBOM与SCA

随着容器、微服务等新技术日新月异&#xff0c;开源软件成为业界主流形态&#xff0c;软件行业快速发展。但同时&#xff0c;软件供应链也越来越趋于复杂化和多样化&#xff0c;软件供应链安全风险不断加剧。 软件供应链安全主要包括软件开发生命周期和软件生存运营周期&#x…

vue3项目创建-配置-elementPlus导入-路由自动导入

目录 方法一&#xff1a;create-vue 方法二 &#xff1a;Vite Vue Vite.config.ts配置 引入element-plus 安装 如何在项目中使用 Element Plus 完整引入 按需导入 vue3vite中自动配置路由的神器&#xff1a;vite-plugin-pages 1. 安装 2、修改vite.config.js中配置…

MUSE Pi Pro 编译kernel内核及创建自动化脚本进行环境配置

视频讲解&#xff1a; MUSE Pi Pro 编译kernel内核及创建自动化脚本进行环境配置 今天分享的主题为创建自动化脚本编译MUSE Pi Pro的kernel内核&#xff0c;脚本已经上传到中 GitHub - LitchiCheng/MUSE-Pi-Pro-Learning: MUSE-Pi-Pro-Learning &#xff0c;有需要可以自行clon…

Innovus 25.1 版本更新:助力数字后端物理设计新飞跃

在数字后端物理设计领域&#xff0c;每一次工具的更新迭代都可能为项目带来巨大的效率提升与品质优化。今天&#xff0c;就让我们一同聚焦 Innovus 25.1 版本&#xff08;即 25.10 版本&#xff09;的更新要点&#xff0c;探寻其中蕴藏的创新能量。 一、核心功能的强势进 AI…

CodeBuddy 中国版 Cursor 实战:Redis+MySQL双引擎驱动〈王者荣耀〉战区排行榜

文章目录 一、引言二、系统架构设计2.1、整体架构概览2.2、数据库设计2.3、后端服务设计 三、实战&#xff1a;从零构建排行榜3.1、开发环境准备3.2、用户与战区 数据管理3.2.1、MySQL 数据库表创建3.2.2、实现用户和战区数据的 CURD 操作 3.3、实时分数更新3.4、排行榜查询3.5…

在线SQL转ER图工具

在线SQL转ER图网站 在数据库设计、软件开发或学术研究中&#xff0c;ER图&#xff08;实体-关系图&#xff09; 是展示数据库结构的重要工具。然而&#xff0c;手动绘制ER图不仅耗时费力&#xff0c;还容易出错。今天&#xff0c;我将为大家推荐一款非常实用的在线工具——SQL…

python高级特性

json.dumps({a:1,n:2}) #Python 字典类型转换为 JSON 对象。相当于jsonify data2 json.loads(json_str)#将 JSON 对象转换为 Python 字典 异步编程&#xff1a;在异步编程中&#xff0c;程序可以启动一个长时间运行的任务&#xff0c;然后继续执行其他任务&#xff0c;而无需等…

汇编:子程序设计

一、 实验要求 实验目的&#xff1a; 熟练掌握算术运算汇编指令的使用熟练掌握子程序设计的基本方法熟练掌握程序的调试方法 实验内容&#xff1a; 编程实现两个数&#xff1a;#8888H和#79H的乘除运算结合实验1的代码&#xff0c;将加减乘除四则运算写成四个子程序&#xff…

从概念表达到安全验证:智能驾驶功能迎来系统性规范

随着辅助驾驶事故频发&#xff0c;监管机制正在迅速补位。面对能力表达、使用责任、功能部署等方面的新要求&#xff0c;行业开始重估技术边界与验证能力&#xff0c;数字样机正成为企业合规落地的重要抓手。 2025年以来&#xff0c;围绕智能驾驶功能的争议不断升级。多起因辅…

DeepSeek基于注意力模型的可控图像生成

DeepSeek大模型高性能核心技术与多模态融合开发 - 商品搜索 - 京东 图像的加噪与模型训练 在扩散模型的训练过程中&#xff0c;首先需要对输入的信号进行加噪处理&#xff0c;经典的加噪过程是在图像进行向量化处理后在其中添加正态分布&#xff0c;而正态分布的值也是与时间…

“端 - 边 - 云”三级智能协同平台的理论建构与技术实现

摘要 随着低空经济与智能制造的深度融合&#xff0c;传统集中式云计算架构在实时性、隐私保护和资源效率上的瓶颈日益凸显。本文提出“端 - 边 - 云”三级智能协同平台架构&#xff0c;以“时空 - 资源 - 服务”三维协同理论为核心&#xff0c;构建覆盖终端感知、边缘计算、云端…

AI时代,如何实现人机共舞?

在科技飞速发展的当下&#xff0c;人工智能&#xff08;AI&#xff09;已不再是科幻作品中的遥远想象&#xff0c;而是深入渗透到我们生活与工作的方方面面。从智能手机中的语音助手&#xff0c;到金融领域的风险预测模型&#xff1b;从医疗影像的智能诊断&#xff0c;到工业生…

OceanBase 在业务监控系统中的应用实践

本文作者来自于一家总部在宁波的新能源上市公司&#xff0c;公司业务包括光伏新能源产品的研发与产销。 作为年产值达百亿的企业&#xff0c;监控系统是不可或缺的IT管理体系之一&#xff0c;对于确保业务连续性及预警风险非常重要。2022年&#xff0c;公司选择把Zabbix作为企业…

每日Prompt:品牌化键盘键帽

提示词 一个超逼真的3D渲染图&#xff0c;展示了四个机械键盘键帽&#xff0c;排列成紧密的2x2网格&#xff0c;所有键帽相互接触。从等轴测角度观察。一个键帽是透明的&#xff0c;上面用红色印刷着“{just}”字样。另外三个键帽采用颜色&#xff1a;{黑色、紫色和白色}。一个…

超声波传感器模块

欢迎来到 破晓的历程的 博客 ⛺️不负时光&#xff0c;不负己✈️ 文章目录 1.HC-SR04介绍2.HC-SR04原理介绍2.1原理概述3.2原理详解 4驱动代码编写4.1写前思考4.2硬件连线 5.总结hcsr04.hhcsr04.c 1.HC-SR04介绍 超声波传感器有很多种类的型号&#xff1a;HC-SR04、UC-025、…

LeetCode 513 找树左下角的值 LeetCode 112 路径总和 LeetCode106 从中序与后序遍历序列构造二叉树

LeetCode 513 找树左下角的值 迭代法——层序遍历 思路&#xff1a;对树进行层序遍历操作&#xff0c;层序遍历完后&#xff0c;输出树最后一层的第一个节点。 # Definition for a binary tree node. # class TreeNode(object): # def __init__(self, val0, leftNone, r…

『大模型笔记』Langchain作者Harrison Chase专访:环境智能体与全新智能体收件箱

Langchain作者Harrison Chase专访:环境智能体与全新智能体收件箱 文章目录 摘要访谈内容什么环境智能体为什么要探索环境智能体怎么让人类能更方便地和环境智能体互动参考文献摘要 LangChain 的 CEO Harrison Chase 提出了_“环境智能体”(Ambient Agents)的概念,这是一种…

SpringBoot的外部化配置

一、什么是外部化配置 外部化配置是指把应用程序中各种可配置的参数、属性等信息&#xff0c;从代码内部提取出来&#xff0c;放置在外部的配置文件、数据库或配置中心等地方&#xff08;比如使用.properties、.yml 或.xml 等格式的文件&#xff09;进行管理。提高应用程序的可…