C++ 智能指针 unique_ptr 详解与示例

2023-05-16

unique_ptrC++ 11 提供的用于防止内存泄漏的智能指针中的一种实现,独享被管理对象指针所有权的智能指针。unique_ptr对象包装一个原始指针,并负责其生命周期。当该对象被销毁时,会在其析构函数中删除关联的原始指针。
unique_ptr具有->*运算符重载符,因此它可以像普通指针一样使用。
查看下面的示例:

#include <iostream>
#include <memory>

struct Task {
    int mId;
    Task(int id ) :mId(id) {
        std::cout << "Task::Constructor" << std::endl;
    }
    ~Task() {
        std::cout << "Task::Destructor" << std::endl;
    }
};

int main()
{
    // 通过原始指针创建 unique_ptr 实例
    std::unique_ptr<Task> taskPtr(new Task(23));

    //通过 unique_ptr 访问其成员
    int id = taskPtr->mId;
    std::cout << id << std::endl;

    return 0;
}

输出:

Task::Constructor
23
Task::Destructor

unique_ptr <Task> 对象 taskPtr 接受原始指针作为参数。现在当main函数退出时,该对象超出作用范围就会调用其析构函数,在unique_ptr对象taskPtr 的析构函数中,会删除关联的原始指针,这样就不用专门delete Task对象了。
这样不管函数正常退出还是异常退出(由于某些异常),也会始终调用taskPtr的析构函数。因此,原始指针将始终被删除并防止内存泄漏。

unique_ptr 独享所有权

unique_ptr对象始终是关联的原始指针的唯一所有者。我们无法复制unique_ptr对象,它只能移动。
由于每个unique_ptr对象都是原始指针的唯一所有者,因此在其析构函数中它直接删除关联的指针,不需要任何参考计数。

创建一个空的 unique_ptr 对象

创建一个空的unique_ptr<int>对象,因为没有与之关联的原始指针,所以它是空的。

std::unique_ptr<int> ptr1;

检查 unique_ptr 对象是否为空

有两种方法可以检查 unique_ptr 对象是否为空或者是否有与之关联的原始指针。

// 方法1
if(!ptr1)
	std::cout<<"ptr1 is empty"<<std::endl;
// 方法2
if(ptr1 == nullptr)
	std::cout<<"ptr1 is empty"<<std::endl;

使用原始指针创建 unique_ptr 对象

要创建非空的 unique_ptr 对象,需要在创建对象时在其构造函数中传递原始指针,即:

std::unique_ptr<Task> taskPtr(new Task(22));

或者(这种新学到的)

std::unique_ptr<Task> taskPtr(new std::unique_ptr<Task>::element_type(23));

不能通过赋值的方法创建对象,下面的这句是错误的

// std::unique_ptr<Task> taskPtr2 = new Task(); // 编译错误

使用 std::make_unique 创建 unique_ptr 对象 / C++14

std::make_unique<>() 是C++ 14 引入的新函数

std::unique_ptr<Task> taskPtr = std::make_unique<Task>(34);

获取被管理对象的指针

使用get()·函数获取管理对象的指针。

Task *p1 = taskPtr.get();

重置 unique_ptr 对象

在 unique_ptr 对象上调用reset()函数将重置它,即它将释放delete关联的原始指针并使unique_ptr 对象为空。

taskPtr.reset();

unique_ptr 对象不可复制

由于 unique_ptr 不可复制,只能移动。因此,我们无法通过复制构造函数或赋值运算符创建unique_ptr对象的副本。

// 编译错误 : unique_ptr 不能复制
std::unique_ptr<Task> taskPtr3 = taskPtr2; // Compile error

// 编译错误 : unique_ptr 不能复制
taskPtr = taskPtr2; //compile error

转移 unique_ptr 对象的所有权

我们无法复制 unique_ptr 对象,但我们可以转移它们。这意味着 unique_ptr 对象可以将关联的原始指针的所有权转移到另一个 unique_ptr 对象。让我们通过一个例子来理解:

// 通过原始指针创建 taskPtr2
std::unique_ptr<Task> taskPtr2(new Task(55));
// 把taskPtr2中关联指针的所有权转移给taskPtr4
std::unique_ptr<Task> taskPtr4 = std::move(taskPtr2);
// 现在taskPtr2关联的指针为空
if(taskPtr2 == nullptr)
	std::cout<<"taskPtr2 is  empty"<<std::endl;

// taskPtr2关联指针的所有权现在转移到了taskPtr4中
if(taskPtr4 != nullptr)
	std::cout<<"taskPtr4 is not empty"<<std::endl;

// 会输出55
std::cout<< taskPtr4->mId << std::endl;

std::move() 将把 taskPtr2 转换为一个右值引用。因此,调用 unique_ptr 的移动构造函数,并将关联的原始指针传输到 taskPtr4。在转移完原始指针的所有权后, taskPtr2将变为空。

释放关联的原始指针

在 unique_ptr 对象上调用 release()将释放其关联的原始指针的所有权,并返回原始指针。这里是释放所有权,并没有delete原始指针,reset()会delete原始指针。

std::unique_ptr<Task> taskPtr5(new Task(55));
// 不为空
if(taskPtr5 != nullptr)
	std::cout<<"taskPtr5 is not empty"<<std::endl;
// 释放关联指针的所有权
Task * ptr = taskPtr5.release();
// 现在为空
if(taskPtr5 == nullptr)
	std::cout<<"taskPtr5 is empty"<<std::endl;

完整示例程序

#include <iostream>
#include <memory>

struct Task {
    int mId;
    Task(int id ) :mId(id) {
        std::cout<<"Task::Constructor"<<std::endl;
    }
    ~Task() {
        std::cout<<"Task::Destructor"<<std::endl;
    }
};

int main()
{
    // 空对象 unique_ptr
    std::unique_ptr<int> ptr1;

    // 检查 ptr1 是否为空
    if(!ptr1)
        std::cout<<"ptr1 is empty"<<std::endl;

    // 检查 ptr1 是否为空
    if(ptr1 == nullptr)
        std::cout<<"ptr1 is empty"<<std::endl;

    // 不能通过赋值初始化unique_ptr
    // std::unique_ptr<Task> taskPtr2 = new Task(); // Compile Error

    // 通过原始指针创建 unique_ptr
    std::unique_ptr<Task> taskPtr(new Task(23));

    // 检查 taskPtr 是否为空
    if(taskPtr != nullptr)
        std::cout<<"taskPtr is  not empty"<<std::endl;

    // 访问 unique_ptr关联指针的成员
    std::cout<<taskPtr->mId<<std::endl;

    std::cout<<"Reset the taskPtr"<<std::endl;
    // 重置 unique_ptr 为空,将删除关联的原始指针
    taskPtr.reset();

    // 检查是否为空 / 检查有没有关联的原始指针
    if(taskPtr == nullptr)
        std::cout<<"taskPtr is  empty"<<std::endl;

    // 通过原始指针创建 unique_ptr
    std::unique_ptr<Task> taskPtr2(new Task(55));

    if(taskPtr2 != nullptr)
        std::cout<<"taskPtr2 is  not empty"<<std::endl;

    // unique_ptr 对象不能复制
    //taskPtr = taskPtr2; //compile error
    //std::unique_ptr<Task> taskPtr3 = taskPtr2;

    {
        // 转移所有权(把unique_ptr中的指针转移到另一个unique_ptr中)
        std::unique_ptr<Task> taskPtr4 = std::move(taskPtr2);
        // 转移后为空
        if(taskPtr2 == nullptr)
            std::cout << "taskPtr2 is  empty" << std::endl;
        // 转进来后非空
        if(taskPtr4 != nullptr)
            std::cout<<"taskPtr4 is not empty"<<std::endl;

        std::cout << taskPtr4->mId << std::endl;

        //taskPtr4 超出下面这个括号的作用于将delete其关联的指针
    }

    std::unique_ptr<Task> taskPtr5(new Task(66));

    if(taskPtr5 != nullptr)
        std::cout << "taskPtr5 is not empty" << std::endl;

    // 释放所有权
    Task * ptr = taskPtr5.release();

    if(taskPtr5 == nullptr)
        std::cout << "taskPtr5 is empty" << std::endl;

    std::cout << ptr->mId << std::endl;

    delete ptr;

    return 0;
}

输出:

ptr1 is empty
ptr1 is empty
Task::Constructor
taskPtr is  not empty
23
Reset the taskPtr
Task::Destructor
taskPtr is  empty
Task::Constructor
taskPtr2 is  not empty
taskPtr2 is  empty
taskPtr4 is not empty
55
Task::Destructor
Task::Constructor
taskPtr5 is not empty
taskPtr5 is empty
66
Task::Destructor

总结

new出来的对象是位于堆内存上的,必须调用delete才能释放其内存。
unique_ptr 是一个装指针的容器,且拥有关联指针的唯一所有权,作为普通变量使用时系统分配对象到栈内存上,超出作用域时会自动析构,unique_ptr对象的析构函数中会delete其关联指针,这样就相当于替我们执行了delete堆内存上的对象。

成员函数作用
reset()重置unique_ptr为空,delete其关联的指针。
release()不delete关联指针,并返回关联指针。释放关联指针的所有权,unique_ptr为空。
get()仅仅返回关联指针

unique_ptr不能直接复制,必须使用std::move()转移其管理的指针,转移后原 unique_ptr 为空。std::unique_ptr<Task> taskPtr4 = std::move(taskPtr2);

创建unique_ptr对象有两种方法:

//C++11: 
std::unique_ptr<Task> taskPtr(new Task(23));
//C++14: 
std::unique_ptr<Task> taskPtr = std::make_unique<Task>(34);
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

C++ 智能指针 unique_ptr 详解与示例 的相关文章

  • 如何加速“独特”数据框搜索

    我有一个数据框 其尺寸为 2377426 行 x 2 列 如下所示 Name Seq 428293 ENSE00001892940 ENSE00001929862 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
  • Javascript:确定字符串中的所有字符是否唯一,如果不唯一,则删除重复字符

    设置一个数组a letter occurences 但努力循环遍历这个数组 以检查occurences gt 1并删除那些存在的 function charFreq s var i j var a new Array for j 0 j l
  • 最有效的方法...唯一的随机字符串

    我需要有效地将 5 个字符的随机字符串插入数据库 同时确保它是唯一的 生成随机字符串不是问题 但目前我正在做的是生成字符串 然后检查数据库是否已经存在 如果存在 我会重新开始 有没有更有效的方法来完成这个过程 请注意 我不想使用 GUID
  • Map/Set 维护唯一的数组数组,Javascript

    我正在尝试构建唯一的数组数组 这样每当我要添加新数组时 只有在集合中尚不存在该数组时才应添加它 例如 存储 1 1 2 的所有唯一排列 实际的 1 1 2 1 2 1 1 1 2 1 2 1 2 1 1 2 1 1 预期的 1 1 2 1
  • 在 C 中查找列表的基数

    我怎样才能只找到列表中出现一次的元素并返回基数 例如 如果我的列表由 3 2 1 1 2 4 组成 我期望返回计数器为 4 而不是6 因为我们不计算重复的数字 这是我到目前为止编写的代码 struct Node int data struc
  • 选择只有一个唯一值的 pandas 数据框列

    如何有效地选择只有 1 个唯一值的 pandas dataframe 列 我知道 DataFrame 和 Series nunique 我认为需要DataFrame nunique http pandas pydata org pandas
  • CQRS 事件溯源:验证用户名唯一性

    我们以一个简单的 账户注册 为例 流程如下 用户访问网站 点击 注册 按钮并填写表格 点击 保存 按钮 MVC 控制器 通过读取 ReadModel 来验证用户名的唯一性 RegisterCommand 再次验证用户名唯一性 这是问题 当然
  • 熊猫标签重复

    给定以下数据框 import pandas as pd d pd DataFrame label 1 2 2 2 3 4 4 values 3 5 7 2 5 8 3 d label values 0 1 3 1 2 5 2 2 7 3 2
  • 从数组中删除非唯一行

    我有一个数组a如下 a 1 2 3 4 1 2 我想删除在中多次出现的所有行a并得到c c 3 4 请注意 这与保留唯一行不同 因为我根本不希望出现重复行 我怎样才能做到这一点 第三个输出为unique https www mathwork
  • MongoDB 多个字段上的唯一索引

    我正在使用 MongoDb 数据库 我需要使多个字段唯一 我需要的是 MongoDb 检查是否组合多个字段的值是唯一的 让我们举一个我需要的例子 如果我在添加索引后按此顺序在数据库中添加以下内容 name paul age 21 name
  • 如何使多维数组变得唯一? [复制]

    这个问题在这里已经有答案了 我有一个多维数组设置 如下所示 array 0 gt array name gt Foo slug gt Bar 1 gt array name gt Foo slug gt Bar 2 gt array nam
  • 在Java中生成随机唯一的双精度值

    我需要一个 64 位浮点随机数的集合 并且它们应该是不同的 是否有为此的库例程 或者我应该手动搜索重复项 实际上 让数字不接近比一些非常小的常数 更重要 图书馆也有这样的例程吗 您可以使用streams为了那个原因 double array
  • postgresql 分区上的唯一索引

    我有一个名为 cdrs 的表 CREATE TABLE cdrs i cdr bigint NOT NULL i cdrs connection bigint NOT NULL i call bigint NOT NULL customer
  • 在 SQL 数据库中保持 RSS 提要唯一的最佳实践

    我正在开发一个项目 该项目显示来自不同站点的 RSS 提要 我将它们保存在数据库中 我的程序每 3 小时获取一次并将它们插入到 SQL 数据库中 我希望提供者有独特的记录 不要显示重复的内容 但问题是一些提供商不提供 GUID 字段 而其他
  • “此列列表没有匹配的唯一键或主键”。但主键确实存在

    所以我正在练习一些 sql 编码来进行测试 但我无法获得外键来引用主键 这是不起作用的表 CREATE TABLE ASSIGNMENT ASSIGN ID NUMBER 2 NOT NULL START DATE DATE END DAT
  • 在 C++ 中检查向量的所有元素是否相等

    如果我有一个值向量并且想要检查它们是否都相同 那么在 C 中有效执行此操作的最佳方法是什么 如果我用其他语言 例如 R 进行编程 我的想法是仅返回容器的唯一元素 然后如果唯一元素的长度大于 1 我知道所有元素不可能相同 在 C 中 可以这样
  • 根据 C# 的属性之一从列表中删除重复的项目

    我有类型类别的列表 public class MyClass public SomeOtherClass classObj public string BillId public List
  • 如何从数组C++中获取唯一的字符串

    我知道我的问题对某些人来说可能很愚蠢 但我整天用谷歌搜索并尝试制定自己的解决方案 但我失败了 请帮助 我需要从简单的字符串数组中打印所有唯一的字符串 example 输入 嗨 我的 名字 嗨 土豆 文本 名字 嗨 输出 我的 土豆 文本 我
  • 迁移范围的独特性

    我一直在尝试找到一种方法来实现这一目标 但我找不到任何尝试 即使如此 我想也许我的方法是完全错误的 也就是说 我应该做什么移民如果我希望两个字段的组合是唯一的 请注意 我不希望它们成为索引 而只是数据库字段 例如 对于下面的迁移 我可以单独
  • Postgresql 强制执行唯一的双向列组合

    我正在尝试创建一个表 该表将在两个方向上强制执行相同类型的两列的唯一组合 例如 这是非法的 col1 col2 1 2 2 1 我已经想出了这个 但它不起作用 database gt d friend Table public friend

随机推荐

  • C++Primer第五版 习题答案 第六章 函数(Functions)

    练习6 1 实参和形参的区别的什么 xff1f 形参在函数的定义中声明 xff1b 实参是形参的初始值 练习6 2 请指出下列函数哪个有错误 xff0c 为什么 xff1f 应该如何修改这些错误呢 xff1f span class toke
  • Dockerfile的CMD指令

    一 Docker的CMD指令 The main purpose of a CMD is to provide defaults for an executing container CMD在容器运行的时候提供一些命令及参数 xff0c 用法
  • usb供电相关

    1 针对集线器端口上的电涌警告 xff1a 在 设备管理器 gt 通用串行总线控制器 xff0d USB Root Hub gt 电源 中可以看到 xff0c 正在使用的USB设备的耗 由于频繁的插拔USB设备 xff0c 或在USB设备传
  • PHP四种设计模式

    1php常见开发模式 xff1a 1 单列模式 2 工厂模式 3 观察者模式 4 策略模式 2设计模式实例 1 单例模式 单例模式顾名思义 xff0c 就是只有一个实例 作为对象的创建模式 xff0c 单例模式确保某一个类只有一个实例 xf
  • CAN总线基础知识(一)

    1 xff0e CAN总线是什么 xff1f CAN xff08 Controller Area Network xff09 是ISO国际标准化的串行通信协议 广泛应用于汽车 船舶等 具有已经被大家认可的高性能和可靠性 CAN控制器通过组成
  • 【ROS Gazebo专题】二、Gazebo的使用上

    跳的比较快 xff0c 别人光介绍基础以及ros的基本操作就写了十几二十篇 xff0c 我一下就跳到了Gazebo这 xff0c 可怕有没有 其实原因很简单 xff0c 如果你将ros官网的基础篇章练习完了 xff0c 在最后一篇 wher
  • 程序员也该懂点UI细节

    虽然说项目开发过程中 xff0c 美工和程序各司其职 但是很多时候程序员本身也要知道一些UI设计的细节 一 每个页面的功能上要突出重点 比如说你首页是想引导更多用户注册的话 xff0c 你就要把注册按钮突出出来 如果你首页是你想引导用户更快
  • ftp 客户端软件的传输模式ASCII和二进制

    FTP可用多种格式传输文件 xff0c 通常由系统决定 xff0c 大多数系统 包括UNIX系统 只有两种模式 xff1a 文本模式和二进制模式 文本传输器使用ASCII字符 xff0c 并由回车键和换行符分开 xff0c 而二进制不用转换
  • 一步一步定制自己的google map(各个省市的经纬度查询)

    安徽省 合肥 北纬31 52 东经117 17 安徽省 安庆 北纬30 31 东经117 02 安徽省 蚌埠 北纬32 56 东经117 21 安徽省 亳州 北纬33 52 东经115 47 安徽省 巢湖 北纬31 36 东经117 52
  • 网页刷新或者重新加载后滚动条的位置不变

    在开发的过程中我们经常需要重新加载或者刷新某个画面 xff0c 已确保数据显示是最新的 但是如果一丁点改变就刷新画面的话 xff0c 会导致用户体验很差 xff0c 想想看你好不容易把网页拖到最后 xff0c 结果点击某个按钮的时候 xff
  • 番茄工作法(番茄钟时间管理)

    番茄工作法是我一次偶然的时间在网上看到的 xff0c 因为自己性格大大咧咧 丢三落四 xff0c 所以经常容易在时间问题上犯迷糊 很多人都有时间拖沓症 xff0c 就是一件事不到最后阶段不去解决它 比如你有一个星期的时间写的毕业论文 xff
  • jquery中美元符号($)命名冲突

    在Jquery中 xff0c 是JQuery的别名 xff0c 所有使用 的地方也都可以使用JQuery来替换 xff0c 如 39 msg 39 等同于JQuery 39 msg 39 的写法 然而 xff0c 当我们引入多个js库后 x
  • Django的密码操作

    一 关于密码操作的思维导图 二 修改密码内置函数源码 64 sensitive post parameters 64 csrf protect 64 login required 64 deprecate current app def p
  • js实现省市联动

    效果图如下 xff1a 思路很简单 xff0c 就是先加载省信息 xff0c 然后当省改变的时候加载市县信息 烦的是数据的录入 xff0c 代码如下 lt DOCTYPE html PUBLIC 34 W3C DTD XHTML 1 0 T
  • 正则在小偷程序中的应用(续)

    获取资源信息 content 61 file get contents 34 http list sososteel com qg list html pg 61 1 amp h 61 34 time 对抓取的信息进行处理 取class为l
  • mysql密码过期问题解决方案

    mysql密码过期问题解决方案 问题再现 xff1a 密码过期 旧电脑许久没有用 xff0c 今天打开发现数据库连接不上了 提示密码过期 xff0c 请修改密码 ERROR 1862 HY000 Your password has expi
  • Fast RTPS(DDS) 安装指南

    Foonathan Memory 在构建 Fast DDS 之前 xff0c 需要先安装 Foonathan Memory 依赖 span class token function git span clone https github c
  • ubuntu的两种软件安装方式

    第一种 xff1a sudo apt get install xxxxxxx 第二种 xff1a sudo dpkg i xxxx deb 参考文章
  • Ethernet下字节序和bit序的总结

    Ethernet下字节序和bit序的总结 本文讲述的是在ethernet中多字节数据发送时涉及到字节序和bit序的剖析 关于字节序 大小端 和bit序 xff0c 以及MSB和LSB的叙述 xff0c 请另行学习 xff0c 本篇不涉及 正
  • C++ 智能指针 unique_ptr 详解与示例

    unique ptr 是 C 43 43 11 提供的用于防止内存泄漏的智能指针中的一种实现 xff0c 独享被管理对象指针所有权的智能指针 unique ptr对象包装一个原始指针 xff0c 并负责其生命周期 当该对象被销毁时 xff0