C++:独占指针(unique_ptr)的理解

2024-08-28 17:12
文章标签 c++ 指针 理解 unique ptr 独占

本文主要是介绍C++:独占指针(unique_ptr)的理解,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

引入

在C++中,动态内存的管理是通过运算符new/delete来完成的:

  • new:在动态内存中为对象分配空间,并且返回一个指向该对象的指针,我们可以选择返回对象对其进行初始化;
  • delete:接受一个动态对象的指针,销毁该对象,并且释放与之关联的内存。

动态分配的对象的生命周期与它们在哪里创建是无关的,只有当显式地被释放时,这些对象才会销毁。

当我们对动态内存的使用不当时,会出现很多麻烦:

  1. 有些内存资源已经被释放,但指向它的指针并没有改变指向(成为了野指针),并且后续还在使用;
  2. 有些内存资源已经被释放,后期又试图再释放一次(重复释放同一块内存会导致程序运行崩溃);
  3. 没有及时释放不再使用的内存资源,造成内存泄漏,程序占用的内存资源越来越多。

为了更容易同时也更安全的使用动态内存,C++11提供了智能指针来管理动态对象。智能指针的行为类似于常规指针,重要的区别在于它负责自动释放所指向的对象。

原理

当我们创建一个类对象时,会自动调用类的默认构造函数。当类对象超出作用域时,会自动调用析构函数来释放资源。

智能指针的实现原理是通过RAII(Resource Acquisition Is Initialization,资源获取即初始化)技术来管理动态分配的内存。它通过在对象的构造函数中获取资源,在对象的析构函数中释放资源,来保证资源的正确使用。

unique_ptr

unique_ptr是独占指针,在同一时刻他只允许一个指针指向对象。

std::unique_ptr<int> u1(new int(2));
auto u2=std::make_unique<int>(2);

由于unique_ptr的独占性,不允许对unique_ptr进行拷贝和赋值。

unique_ptr(const unique_ptr&)=delete;
unique_ptr& operator=(const unique_ptr&)=delete;

可以利用std::move将对象所有权从一个unique_ptr转移给另一个unique_ptr。转移后,原来的unique_ptr将不再拥有对内存的控制权,将变为空指针。

//1.使用release来转移
std::unique_ptr<Test> u1=std::make_unique<Test>(11);
std::unique_ptr<Test> u2(u1.release());//2.使用move来转移
std::unique_ptr<Test> u1=std::make_unique<Test>(11);
std::unique_ptr<Test> u2(std::move(u1));

由于unique_ptr不能被拷贝,所以把unique_ptr作为参数类型会报错

void testUniquePtr(std::unique_ptr<Test> &ptr) {}

如果函数的参数不是引用类型,并且外部不再需要使用该指针,我们可以使用move()或者release()把该对象的控制权转移,将其交由调用的函数管理

void testUniquePtr(std::unique_ptr<Test> ptr) {} //作用域结束,将会释放ptr所指向的对象int main(){std::unique_ptr<Test> up = std::make_unique<Test>(100);//将对象的唯一控制权交给了函数//函数结束后,对象被释放testUniquePtr(std::unique_ptr<Test>(std::move(up)));return 0;
}

如果想把把unique_ptr作为参数返回时,可以调用其move()方法,同时还可以把返回的unique_ptr转换为shared_ptr

//使用move操作,而非拷贝构造函数
std::unique_ptr<Test> test(int i) {return std::make_unique<Test>(i);
}int main(){//使用move给up赋值,而非拷贝构造函数std::unique_ptr<Test> up = test(100);//可以把一个对象的控制权交由shared_ptr来管理std::shared_ptr<Test> sp = test(100);return 0;
}

自定义删除器

unique_ptr的模板类型为:

template<typename __Tp,typename __Dp=default_delete<__Tp> >
class unique_ptr{
......
};

模板的参数,第一个为unique_ptr关联的原始指针类型,后者为删除器,默认值为default_delete、删除器是unique_ptr类型的组成部分,可以是普通函数指针、函数对象或者lambda表达式。当需要自定义删除器时,我们需要指定其类型,即__Dp不可省略,我们可以通过decltype来获得其类型。

//1.函数指针
void myDeleter(Test* p){delete p;
}std::unique_ptr<Test,decltype<&myDeleter) > ptr1(new Test(),myDeleter);//2.函数对象
class MyDeleter{
public:void operator()(int *p){delete[] p;}
};
std::unique_ptr<int[],MyDeleter> p2(new int[100],MyDeleter());//3.lambda表达式std::unique_ptr<int,void(*)(int*)> p3(new int(1),[](int *p){ delete p;});

常用函数

get():返回智能指针中保存的原生指针

reset():释放原生指针,并将原指针置空

reset(q):释放原生指针,使智能指针指向新的原生指针。

swap(p,q):交换智能指针p和q的原生指针

独占指针unique_ptr不需要维护引用计数和原子操作,因此比共享指针shared_ptr所消耗的内存更少,性能也更好。

template<class T>
class Unique_Ptr{
private:T* m_p;
public:explicit Unique_Ptr(T* p=nullptr):m_p(p) {}~Unique_Ptr(){if(m_p)delete m_p;}Unique_Ptr(const Unique_Ptr& )=delete;Unique_Ptr& operator=(const Unique_Ptr&)=delete;//移动构造函数Unique_Ptr(Unique_Ptr&& that) noexcept :m_p(that.m_p){that.m_p=nullptr;}//移动赋值函数Unique_Ptr& operator=(Unique_Ptr&& that) noexcept {if(this!=&that){if(m_p!=nullptr){delete m_p;}m_p=that.m_p;that.m_p=nullptr;}return *this;}void swap(Unique_Ptr& that) noexcept {std::swap(m_p,that.m_p);}T* get() const noexcept {return m_p;}T* release() noexcept {T* tmp=m_p;m_p=nullptr;return tmp;}void reset() noexcept {if(m_p!=nullptr)delete m_p;m_p=nullptr;      }void reset(T* p) noexcept {if(m_p!=nullptr)delete m_p;m_p=p;}T& operator*() noexcept {return *m_p;}T* operator->() noexcept {return m_p;}explicit operator bool() const noexcept {return m_p != nullptr;}bool operator==(Unique_Ptr const& that) const noexcept {return m_p == that.m_p;}bool operator!=(Unique_Ptr const& that) const noexcept {return m_p != that.m_p;}};

这篇关于C++:独占指针(unique_ptr)的理解的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1115383

相关文章

使用C++实现链表元素的反转

《使用C++实现链表元素的反转》反转链表是链表操作中一个经典的问题,也是面试中常见的考题,本文将从思路到实现一步步地讲解如何实现链表的反转,帮助初学者理解这一操作,我们将使用C++代码演示具体实现,同... 目录问题定义思路分析代码实现带头节点的链表代码讲解其他实现方式时间和空间复杂度分析总结问题定义给定

C++初始化数组的几种常见方法(简单易懂)

《C++初始化数组的几种常见方法(简单易懂)》本文介绍了C++中数组的初始化方法,包括一维数组和二维数组的初始化,以及用new动态初始化数组,在C++11及以上版本中,还提供了使用std::array... 目录1、初始化一维数组1.1、使用列表初始化(推荐方式)1.2、初始化部分列表1.3、使用std::

C++ Primer 多维数组的使用

《C++Primer多维数组的使用》本文主要介绍了多维数组在C++语言中的定义、初始化、下标引用以及使用范围for语句处理多维数组的方法,具有一定的参考价值,感兴趣的可以了解一下... 目录多维数组多维数组的初始化多维数组的下标引用使用范围for语句处理多维数组指针和多维数组多维数组严格来说,C++语言没

c++中std::placeholders的使用方法

《c++中std::placeholders的使用方法》std::placeholders是C++标准库中的一个工具,用于在函数对象绑定时创建占位符,本文就来详细的介绍一下,具有一定的参考价值,感兴... 目录1. 基本概念2. 使用场景3. 示例示例 1:部分参数绑定示例 2:参数重排序4. 注意事项5.

使用C++将处理后的信号保存为PNG和TIFF格式

《使用C++将处理后的信号保存为PNG和TIFF格式》在信号处理领域,我们常常需要将处理结果以图像的形式保存下来,方便后续分析和展示,C++提供了多种库来处理图像数据,本文将介绍如何使用stb_ima... 目录1. PNG格式保存使用stb_imagephp_write库1.1 安装和包含库1.2 代码解

C++实现封装的顺序表的操作与实践

《C++实现封装的顺序表的操作与实践》在程序设计中,顺序表是一种常见的线性数据结构,通常用于存储具有固定顺序的元素,与链表不同,顺序表中的元素是连续存储的,因此访问速度较快,但插入和删除操作的效率可能... 目录一、顺序表的基本概念二、顺序表类的设计1. 顺序表类的成员变量2. 构造函数和析构函数三、顺序表

使用C++实现单链表的操作与实践

《使用C++实现单链表的操作与实践》在程序设计中,链表是一种常见的数据结构,特别是在动态数据管理、频繁插入和删除元素的场景中,链表相比于数组,具有更高的灵活性和高效性,尤其是在需要频繁修改数据结构的应... 目录一、单链表的基本概念二、单链表类的设计1. 节点的定义2. 链表的类定义三、单链表的操作实现四、

使用C/C++调用libcurl调试消息的方式

《使用C/C++调用libcurl调试消息的方式》在使用C/C++调用libcurl进行HTTP请求时,有时我们需要查看请求的/应答消息的内容(包括请求头和请求体)以方便调试,libcurl提供了多种... 目录1. libcurl 调试工具简介2. 输出请求消息使用 CURLOPT_VERBOSE使用 C

C++实现获取本机MAC地址与IP地址

《C++实现获取本机MAC地址与IP地址》这篇文章主要为大家详细介绍了C++实现获取本机MAC地址与IP地址的两种方式,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 实际工作中,项目上常常需要获取本机的IP地址和MAC地址,在此使用两种方案获取1.MFC中获取IP和MAC地址获取

C/C++通过IP获取局域网网卡MAC地址

《C/C++通过IP获取局域网网卡MAC地址》这篇文章主要为大家详细介绍了C++如何通过Win32API函数SendARP从IP地址获取局域网内网卡的MAC地址,感兴趣的小伙伴可以跟随小编一起学习一下... C/C++通过IP获取局域网网卡MAC地址通过win32 SendARP获取MAC地址代码#i