1 smart pointer 思想

​ 个人认为smart pointer实际上就是一个对原始指针类型的一个封装类,并对外提供了-> 和 * 两种操作,使得其能够表现出原始指针的操作行为。

​ 要理解smart pointer思想首先要了解一个概念RAII(Resource Acquisition Is Initialization), 直译为资源获取即初始化,核心理念为在对象创建时分配资源,而在对象销毁时释放资源.

​ 根据RAII理念,如果对象创建在栈(stack)上,由于栈上的对象在销毁是会自动调用析构函数,因此仅仅需要在构造函数内完成资源分配,而在析构函数内完成资源释放,此时程序员就不需要自己关心资源的释放问题。

​ 但当对象创建在自由存储区(free store)上时,例如:

class Fruit {
public:
Fruit(std::string name = "fruit", int num = 1) :name_{ name }, num_{ num }{}
~Fruit(){ cout << "destroy fruit" << endl;}
std::string name_;
int num_;
}; int main(){
Fruit* intPtr{new Fruit};//memory leak
return 0;
}

此时系统仅仅能回收在栈上1创建的指针intPtr所占据的资源,对于指针所指向的动态分配的内存空间并不会自动调用析构函数进行资源释放,此时如果程序员不主动调用 delete 进行资源释放则会产生内存泄漏

​ 那么如何让创建在自由存储区的对象也能够自动地释放资源,而不需要程序员自己手动释放资源呢?智能指针给出了一种非常巧妙的解决思路,它将一个原本定义在自由存储区的对象封装进了一个创建在栈上的资源管理对象中,由这个资源管理对象在自己的析构函数中释放定义在自由存储区上的对象所占据的资源。这使得程序员只需要利用资源管理对象接管在自由存储区上动态创建的对象资源,利用栈对象的生存机制能够实现资源的自动释放而不需要自己手动delete 对象资源。例如:

template <typename T>
class ResourceManager {
public:
ResourceManager(T* ptr) :ptr_{ ptr } {}
~ResourceManager() {
cout << "delete arr in free store" << endl;
delete ptr_;
} private:
T* ptr_;
}; void AutoManage(){
ResourceManager fruit{ new Fruit};
} int main(){
AutoManage();//delete arr in free store
system("pause");
//cout << fruit->name_ << " " << (*fruit).num_ << endl;//fruit 1
return 0;
}

在AutoManage()函数中动态分配一个Fruit对象,并将其封装进ResourceManager资源管理类中,当程序离开函数AutoManage()时,由于ResourceManager是一个定义在栈上的对象,程序会自动调用析构函数~ResourceManager()进行对象销毁操,此时由于ResourceManager在析构函数中进行了Fruit资源的释放,因此不会发生内存泄漏问题,一次不需要程序员手动释放资源的自动内存管理过程完美完成。

​ 以上仅仅完成了动态分配的资源的自动回收功能,要使得ResourceManager资源管理类能够像Fruit*指针一样操作Fruit对象的成员,还需要对外提供***** 以及->两种指针操作:

template <typename T>
class ResourceManager {
public:
ResourceManager(T* ptr) :ptr_{ ptr } {}
~ResourceManager() {
cout << "delete arr in free store" << endl;
delete ptr_;
}
T*& operator->() {return ptr_;}
T& operator*() { return *ptr_; } private:
T* ptr_;
}; void AutoManage(){
ResourceManager fruit{ new Fruit};
} int main(){
AutoManage();//delete arr in free store
system("pause");
cout << fruit->name_ << " " << (*fruit).num_ << endl;//fruit 1
return 0;
}

此时可以利用ResourceManager提供的***** 以及->操作符直接操作原始Fruit* 指针,使得ResourceManager对象就像一个真实的指向Fruit对象的Fruit* 指针。

2 unique_ptr 思想

unique_ptr作为最常用的智能指针,它提供了对资源的独占式管理,即对资源的唯一所有权(sole ownership), 这就要求unique_ptr是一个不可复制的对象。每一个unique_ptr对象都有义务对其管理的资源进行释放。但unique_ptr 并不限制移动(move)操作所导致的所有权转移。最后不要忘记unique_ptr作为一个智能指针概念,它必须能够自动管理动态分配的对象资源,并且提供对对象资源的指针操作。概括一下,unique_ptr要求:

  1. 不可复制
  2. 能够移动
  3. 自动内存管理
  4. 指针操作
template<typename T>
class UniquePtr {
public:
UniquePtr(T* ptr):ptr_{ptr}{}
~UniquePtr() {
cout << "delete unique resource in free store" << endl;
delete ptr_;//释放资源
}
UniquePtr(const UniquePtr&) = delete;//禁用拷贝构造
UniquePtr& operator=(const UniquePtr&) = delete;//禁用拷贝复制
UniquePtr(UniquePtr&& object) {//移动构造
cout << "move construct" << endl;
ptr_ = object.ptr_;
object.ptr_ = nullptr;
}
UniquePtr& operator=(UniquePtr&& object) {//移动赋值
cout << "move assign" << endl;
ptr_ = object.ptr_;
object.ptr_ = nullptr;
return *this;
}
T*& operator->() { return ptr_; }//->
T& operator*() { return *ptr_; }//* private:
T* ptr_;
}; template <typename T>
void ChangeOwnership(UniquePtr<T> move) {
UniquePtr<T> newOwner{ nullptr };
newOwner = std::move(move);
} int main(){
UniquePtr uniquePtr{new Fruit};
ChangeOwnership(std::move(uniquePtr));
//ChangeOwnership(uniquePtr);//compile error! deny copy construction
//UniquePtr uniquePtr1 = uniquePtr;//compile error! deny copy construction
//UniquePtr<Fruit> uniquePtr2{nullptr};
//uniquePtr2 = uniquePtr;//compile error! deny copy assignment
system("pause");
return 0;
}

​ 可以看到即使程序员没有自动释放创建在自由存储区上的对象,通过UniquePtr也能自动进行释放。同时UniquePtr无法进行拷贝,保证了UniquePtr对资源所有权的独占性,而通过std::move() 以及移动构造/赋值函数,UniquePtr能够将对资源的所有权转移给其他UniquePtr对象。基本简易得实现了一个std::unique_ptr智能指针。

3 shared_ptr 思想

shared_ptr作为另一个常用的智能指针,它和unique_ptr智能指针的理念有着很大的不同,它提供了对资源共享管理,即对资源所有权的共享(shared ownership),这就要求shared_ptr必须是一个可复制的对象。但是由于shared_ptr对象有很多个,而具体的对象资源只有一个这就要求所有共享对象资源的shared_ptrs指针中最终只能有一个shared_ptr能够释放对象资源。因此shared_ptr引入了引用计数(reference counting)机制:多个shared_ptrs对象共享一个引用计数变量,通过引用计数记录当前对对象资源被引用的次数,仅当引用计数为0,也就是出当前shared_ptr对象外没有其他shared_ptr对象再共享当前对象资源时,当前shared_ptr对象才能够释放持有的对象资源。

​ 显然根据引用计数(reference counting)机制,释放对象资源的shared_ptr对象必然是最后一个持有对象资源的shared_ptr,这就很好得解决了另一个非常常见的内存问题:重复删除(double deletion)。最后概括一下,shared_ptr要求:

  1. 可复制
  2. 共享引用计数
  3. 自动内存管理
  4. 指针操作
template <typename T>
class SharedPtr {
public:
SharedPtr(T* ptr) :ptr_{ ptr }, count_{ new unsigned int{} } {}
~SharedPtr() {
if (*count_ == 0) {//引用计数==0,释放资源
cout << "delete shared resource in free store" << endl;
delete ptr_;
delete count_;
}
else//引用计数不为0,引用计数-1
--(*count_);
}
SharedPtr(const SharedPtr& object) :ptr_{ object.ptr_ }{//拷贝构造 引用+1
count_ = object.count_;
++(*count_);
}
SharedPtr& operator=(const SharedPtr& object) {//拷贝赋值 引用+1
ptr_ = object.ptr_;
count_ = object.count_;
++(*count_);
return *this;
}
unsigned int GetReferenceCount() { return *count_; }//输出当前资源引用个数
T*& operator->() { return ptr_; }//->
T& operator*() { return *ptr_; }//* private:
T* ptr_;
unsigned int* count_;//reference counting
}; template <typename T>
void ShareOwnership(SharedPtr<T> copy) {
cout << copy.GetReferenceCount() << endl;
}; int main(){
SharedPtr sharedPtr1{new Fruit};
SharedPtr sharedPtr2{ sharedPtr1 };
SharedPtr<Fruit> sharedPtr3{ nullptr };
sharedPtr3 = sharedPtr2;
ShareOwnership(sharedPtr3);
system("pause");
return 0;
}

​ 可以看到即使程序中存在多个shared_ptr对象,共享的Fruit对象资源也只会被释放一次。函数ShareOwnership()中的引用输出为3,这是因为:首先sharedPtr1持有了一个Fruit对象资源,初始化引用为0;其次sharedPtr2,sharedPtr3通过拷贝sharedPtr1的方式共享了Fruit对象资源,这使得引用0+2=2;最后将sharedPtr3拷贝至函数ShareOwnership()的参数copy中时又使得Fruit对象资源的共享者+1,最终使得引用计数2+1=3;

​ 最后补充一点,对于Fruit对象资源的共享,尽量采用直接拷贝shared_ptr对象的方式进行。如果利用原始Fruit* 指针创建新的shared_ptr对象,则很容易产生 重复删除(double deletion)问题:

auto sharedPtr{ std::make_shared<Fruit>("apple",2) };
//sharedPtr.get()返回Fruit对象的原始指针Fruit*
std::shared_ptr<Fruit> sharedPtr1{sharedPtr.get() };//cause double deletion

这是因为sharedPtr,sharedPtr1互相不知道对方的存在,都认为只有自己持有Fruit对象,导致两个shared_ptr的引用计数均为0,当程序走出作用范围后sharedPtr,sharedPtr1都会尝试释放Fruit对象,产生重复删除(double deletion).

智能指针思想实践(std::unique_ptr, std::shared_ptr)的更多相关文章

  1. 12.动态内存和智能指针、 直接管理内存、shared_ptr和new结合使用

    12.动态内存和智能指针 1.智能指针分为两种shared_ptr和unique_ptr,后者独占所指向的对象.智能指针也是模板,使用时要用尖括号指明指向的类型.类似emplace成员,make_sh ...

  2. 智能指针剖析(上)std::auto_ptr与boost::scoped_ptr

    1. 引入 C++语言中的动态内存分配没有自动回收机制,动态开辟的空间需要用户自己来维护,在出函数作用域或者程序正常退出前必须释放掉. 即程序员每次 new 出来的内存都要手动 delete,否则会造 ...

  3. C++智能指针剖析(上)std::auto_ptr与boost::scoped_ptr

    1. 引入 C++语言中的动态内存分配没有自动回收机制,动态开辟的空间需要用户自己来维护,在出函数作用域或者程序正常退出前必须释放掉. 即程序员每次 new 出来的内存都要手动 delete,否则会造 ...

  4. 智能指针剖析(下)boost::shared_ptr&其他

    1. boost::shared_ptr 前面我已经讲解了两个比较简单的智能指针,它们都有各自的优缺点.由于 boost::scoped_ptr 独享所有权,当我们真真需要复制智能指针时,需求便满足不 ...

  5. C++智能指针剖析(下)boost::shared_ptr&其他

    1. boost::shared_ptr 前面我已经讲解了两个比较简单的智能指针,它们都有各自的优缺点.由于 boost::scoped_ptr 独享所有权,当我们真真需要复制智能指针时,需求便满足不 ...

  6. STL 智能指针

    转自: https://blog.csdn.net/k346k346/article/details/81478223 STL一共给我们提供了四种智能指针:auto_ptr.unique_ptr.sh ...

  7. 转载:STL四种智能指针

    转载至:https://blog.csdn.net/K346K346/article/details/81478223 STL一共给我们提供了四种智能指针: auto_ptr.unique_ptr.s ...

  8. C++智能指针简单剖析

    导读 最近在补看<C++ Primer Plus>第六版,这的确是本好书,其中关于智能指针的章节解析的非常清晰,一解我以前的多处困惑.C++面试过程中,很多面试官都喜欢问智能指针相关的问题 ...

  9. C++之智能指针

    导读 一直对智能指针有一种神秘的赶脚,虽然平时没怎么用上智能指针,也就看过STL中的其中一种智能指针auto_ptr,但是一直好奇智能指针的设计因此,今天看了一下<C++ Primer Plus ...

随机推荐

  1. python学习-Day9

    目录 记忆不清点回顾 今日概要 今日内容 大作业讲解 字符编码实际应用 编码与解码 如何解决乱码的问题 文件操作简介 什么是文件 代码操作文件 代码操作文件的流程 基本语法结构 使用关键字打开文件 w ...

  2. 《计算机组成原理/CSAPP》网课总结(一)

    现在是2022年4月17日晚10点,本月计划的网课<csapp讲解>视频课看到了第八章"异常"第三讲,视频讲的很好但更新很慢,暂时没有最新的讲解,所以先做一个简单总结. ...

  3. Go 项目配置文件的定义和读取

    前言 我们在写应用时,基本都会用到配置文件,从各种 shell 到 nginx 等,都有自己的配置文件.虽然这没有太多难度,但是配置项一般相对比较繁杂,解析.校验也会比较麻烦.本文就给大家讲讲我们是怎 ...

  4. re模块,正则表达式起别名和分组机制,collections模块,time与datetime模块,random模块

    re模块和正则表达式别名和分组机制 命名分组 (1)分组--可以让我们从文本内容中提取指定模式的部分内容,用()来表示要提取的分组,需要注意的是分组 是在整个文本符合指定的正则表达式前提下进行的进一步 ...

  5. WPF全局异常处理

    private void RegisterEvents() { //Task线程内未捕获异常处理事件 TaskScheduler.UnobservedTaskException += TaskSche ...

  6. git 撤销远程 commit

    参考: https://blog.csdn.net/xs20691718/article/details/51901161 https://www.cnblogs.com/lfxiao/p/93787 ...

  7. 好客租房3-React的基本使用

    2.1React的安装 安装命令:npm i react react-dom react 包是核心,提供创建元素,组件等功能 react-dom包提供DOM相关功能等 2.2React的使用 1引入r ...

  8. 150_1秒获取Power BI Pro帐号

    博客:www.jiaopengzi.com 请点击[阅读原文]获取帐号 一.背景 当你来到这篇文章的时候,我想你已经在网上搜索了一圈了.网上有一大把教你如何注册Power BI帐号的方法,我们这里就不 ...

  9. HIVE 数据分析

    题目要求: 具体操作: ①hive路径下建表:sale create table sale (day_id String, sale_nbr String, buy_nbr String, cnt S ...

  10. [论文][表情识别]Towards Semi-Supervised Deep Facial Expression Recognition with An Adaptive Confidence Margin

    论文基本情况 发表时间及刊物/会议:2022 CVPR 发表单位:西安电子科技大学, 香港中文大学,重庆邮电大学 问题背景 在大部分半监督学习方法中,一般而言,只有部分置信度高于提前设置的阈值的无标签 ...