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

智能指针 unique_ptr 详解

一、智能指针

1.什么是智能指针

简单地说,C++智能指针是包含重载运算符的类,其行为像常规指针,但智能指针能够及时、妥善地销毁动态分配的数据,并实现了明确的对象生命周期,因此更有价值。

2.常规指针存在的问题

C++在内存分配、释放和管理方面向程序员提供了全面的灵活性。但是这种灵活性是把双刃剑,一方面它使C++成为一种功能强大的语言,另一方面它让程序员能够制造与内存相关的问题,比如内存泄漏(用完未释放,遗漏掉了)。

例如在堆声明和分配的内存,析构方法是否会自动销毁对象,又或是方法结束后需要一个个释放,方法存在很多返回的地方,每个返回语句都要执行很多相关释放操作,十分繁琐,用智能指针就能实现其自动释放。

3.智能指针有何帮助

鉴于使用常规指针存在的问题,当C++程序猿需要管理堆中的数据时,可使用智能指针的方式分配和管理内存。

4.智能指针

智能指针类重载了解除引用运算符(*)和成员选择运算符(->)。

同时为了能够在堆中管理各种类型,几乎所有的智能指针都是模板类,包含其功能的泛型实现

二、unique_ptr 简介

1.概念

unique_ptr 是从 C++ 11 开始,定义在 <memory> 中的智能指针(smart pointer)。它持有对对象的独有权,即两个 unique_ptr 不能指向一个对象,不能进行复制操作只能进行移动操作。

unique_ptr 之所以叫这个名字,是因为它只能指向一个对象,即当它指向其他对象时,之前所指向的对象会被摧毁。其次,当 unique_ptr 超出作用域时,指向的对象也会被自动摧毁,帮助程序员实现了自动释放的功能。

unique_ptr 也可能还未指向对象,这时的状态被称为 empty。

2.简单示例

std::unique_ptr<int>p1(new int(5));
std::unique_ptr<int>p2=p1;// 编译会出错
std::unique_ptr<int>p3=std::move(p1);// 转移所有权, 现在那块内存归p3所有, p1成为无效的针.
p3.reset();//释放内存.
p1.reset();//无效

三、unique_ptr 详解

1.定义

unique_ptr 在 <memory> 中的定义如下:

// non-specialized 
template <class T, class D = default_delete<T>> class unique_ptr;
// array specialization   
template <class T, class D> class unique_ptr<T[],D>;

其中 T 指其管理的对象类型D 指该对象销毁时所调用的释放方法,可以使用自定义的删除器,他也有一个默认的实现,即 detele 操作。

2.常用方法介绍

2.1 构造方法 std::unique_ptr::unique_ptr

// unique_ptr constructor example
#include <iostream>
#include <memory>
 
int main () {
  std::default_delete<int> d;
  std::unique_ptr<int> u1;
  std::unique_ptr<int> u2 (nullptr);
  std::unique_ptr<int> u3 (new int);
  std::unique_ptr<int> u4 (new int, d);
  std::unique_ptr<int> u5 (new int, std::default_delete<int>());
  std::unique_ptr<int> u6 (std::move(u5));
  std::unique_ptr<int> u7 (std::move(u6));
  std::unique_ptr<int> u8 (std::auto_ptr<int>(new int));
 
  std::cout << "u1: " << (u1?"not null":"null") << '\n';
  std::cout << "u2: " << (u2?"not null":"null") << '\n';
  std::cout << "u3: " << (u3?"not null":"null") << '\n';
  std::cout << "u4: " << (u4?"not null":"null") << '\n';
  std::cout << "u5: " << (u5?"not null":"null") << '\n';
  std::cout << "u6: " << (u6?"not null":"null") << '\n';
  std::cout << "u7: " << (u7?"not null":"null") << '\n';
  std::cout << "u8: " << (u8?"not null":"null") << '\n';
 
  return 0;
}

执行结果为:

u1: null
u2: null
u3: not null
u4: not null
u5: null
u6: null
u7: not null
u8: not null

2.2 析构方法 std::unique_ptr::~unique_ptr

// unique_ptr destructor example
#include <iostream>
#include <memory>
 
int main () {
  // user-defined deleter
  auto deleter = [](int*p){
    delete p;
    std::cout << "[deleter called]\n";
  };
  std::unique_ptr<int,decltype(deleter)> foo (new int,deleter);
  std::cout << "foo " << (foo?"is not":"is") << " empty\n";
  return 0; // [deleter called]
}

执行结果为:

foo is not empty
[deleter called]

2.3 释放方法 std::unique_ptr::release

注意!注意!注意!这里的释放并不会销毁其指向的对象,而且将其指向的对象释放出去

// unique_ptr::release example
#include <iostream>
#include <memory>
 
int main () {
  std::unique_ptr<int> auto_pointer (new int);
  int * manual_pointer;
  *auto_pointer=10;
  manual_pointer = auto_pointer.release();
  // (auto_pointer is now empty)
  std::cout << "manual_pointer points to " << *manual_pointer << '\n';
  delete manual_pointer;
  return 0;
}

执行结果为:

manual_pointer points to 10

2.4 重置方法 std::unique_ptr::reset

// unique_ptr::reset example
#include <iostream>
#include <memory>
 
int main () {
  std::unique_ptr<int> up;  // empty
  up.reset (new int);       // takes ownership of pointer
  *up=5;
  std::cout << *up << '\n';
  up.reset (new int);       // deletes managed object, acquires new pointer
  *up=10;
  std::cout << *up << '\n';
  up.reset();               // deletes managed object
  return 0;
}

执行结果为:

5
10

2.5 交换方法 std::unique_ptr::swap

// unique_ptr::swap example
#include <iostream>
#include <memory>
 
int main () {
  std::unique_ptr<int> foo (new int(10));
  std::unique_ptr<int> bar (new int(20));
  foo.swap(bar);
  std::cout << "foo: " << *foo << '\n';
  std::cout << "bar: " << *bar << '\n';
  return 0;
}

执行结果为:

foo: 20
bar: 10

四、自定义删除器详解

1.为什么要使用自定义的删除器

当你的对象不能仅仅只是依靠 delete 删除时,那么你就需要依靠自定义的删除器了。例如对象中还有其它对象的数组等。

2.怎么写

主要就是自己编写一个删除器,指定输入的参数,然后实现相应的释放操作,并将这个删除器传入 unique_ptr 中。

3.实例分析

3.1 开发中经常会使用到图片,假设有这么一个图片的结构体定义如下

/// 图像格式定义
typedef struct cv_image_t {
    unsigned char *data;            ///< 图像数据指针
    cv_pixel_format pixel_format;   ///< 像素格式
    int width;                      ///< 宽度(以像素为单位)
    int height;                     ///< 高度(以像素为单位)
    int stride;                     ///< 跨度, 即每行所占的字节数
} cv_image_t;

3.2 再写一个图片的释放方法

void cv_image_release(cv_image_t* image) {
    if(!image) {
        return;
    }
    delete[] image->data;
    delete image;
    return;
}

3.3 然后我们在代码中就可以使用一个如下的智能指针来智能的控制图片的释放操作了

cv_image_t image_input = { 
    (unsigned char*) image,
    pixel_format,
    image_width,
    image_height,
    image_stride
};
std::unique_ptr<cv_image_t, decltype(cv_image_release)*> image_guard (image_input , cv_image_release);

3.4 优化写法

应用中很多地方都要使用图片,然后都需要这样一个图片的智能指针,不过每次都写这么一长串很麻烦,而且定义也都是一样的,那么我们就可以简单的封装一下,比如可以定义一个 cv_image_ptr 专门,如下:

struct cv_image_destructor{
    void operator()(cv_image_t* img){
        cv_image_release(img);
    }
};
 
typedef std::unique_ptr<cv_image_t, cv_image_destructor> cv_image_ptr;

这样我们再在代码中使用的时候就方便很多了,例如

cv_image_ptr image_guard;
image_guard.reset(image_input);

而且代码看上去也整洁了很多。

关于 unique_ptr 更官方和详细的说明,可以参考:unique_ptr - C++ Reference

相关文章:

  • C++11中“= delete;“的使用
  • C++Error2208:...尝试引用已删除的函数
  • Ninja 构建系统
  • ICU
  • 交叉编译详解
  • GYP,GN和Ninja
  • Visual C++ 新增功能(2003 - 2015)
  • v8引擎编译全记录2021-2-23
  • c++ mutex
  • windows标准控件的介绍与使用
  • VS2019安装 VisualSVN Server 插件
  • cef / JavaScript集成
  • MFC RichEdit使用方法
  • 代码页
  • WM_COMMAND介绍和用法
  • Angular6错误 Service: No provider for Renderer2
  • co.js - 让异步代码同步化
  • java8-模拟hadoop
  • JavaScript 奇技淫巧
  • Redis的resp协议
  • SQL 难点解决:记录的引用
  • Transformer-XL: Unleashing the Potential of Attention Models
  • vue-router的history模式发布配置
  • vue脚手架vue-cli
  • 工程优化暨babel升级小记
  • 基于Dubbo+ZooKeeper的分布式服务的实现
  • 开源地图数据可视化库——mapnik
  • 前端之React实战:创建跨平台的项目架构
  • 什么是Javascript函数节流?
  • 使用Maven插件构建SpringBoot项目,生成Docker镜像push到DockerHub上
  • 数组大概知多少
  • 用 Swift 编写面向协议的视图
  • #、%和$符号在OGNL表达式中经常出现
  • #pragma pack(1)
  • (04)Hive的相关概念——order by 、sort by、distribute by 、cluster by
  • (C语言)输入一个序列,判断是否为奇偶交叉数
  • (MATLAB)第五章-矩阵运算
  • (pojstep1.3.1)1017(构造法模拟)
  • (webRTC、RecordRTC):navigator.mediaDevices undefined
  • (二)c52学习之旅-简单了解单片机
  • (十三)Flask之特殊装饰器详解
  • (译) 函数式 JS #1:简介
  • (原創) 如何解决make kernel时『clock skew detected』的warning? (OS) (Linux)
  • (转载)深入super,看Python如何解决钻石继承难题
  • **PHP分步表单提交思路(分页表单提交)
  • .NET CLR基本术语
  • .Net Core webapi RestFul 统一接口数据返回格式
  • .NET Framework 3.5中序列化成JSON数据及JSON数据的反序列化,以及jQuery的调用JSON
  • .Net Memory Profiler的使用举例
  • .net 打包工具_pyinstaller打包的exe太大?你需要站在巨人的肩膀上-VC++才是王道
  • .NET关于 跳过SSL中遇到的问题
  • .net连接MySQL的方法
  • /dev下添加设备节点的方法步骤(通过device_create)
  • @RequestMapping 的作用是什么?
  • @德人合科技——天锐绿盾 | 图纸加密软件有哪些功能呢?