IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> 智能指针用法及其代码详解 -> 正文阅读

[C++知识库]智能指针用法及其代码详解

网络上大多有关智能指针的解析只停留于简单的字面理解,今天来详细解析一下三种智能指针的用法以及具体的代码。

目录

概念

RAII机制介绍

智能指针雏形

shared_ptr原理介绍

shared_ptr使用方法

unique_ptr

weak_ptr


概念

智能指针不是一个指针,它其实是一个对象。它是通过C++的RAII机制实现的。主要是利用C++中对象在释放的时候,会自动调用析构函数这一特性。

所以,当智能指针对象释放的时候,在智能指针对象的析构函数中来释放其管理的内存资源。这样,开发人员就不需要手动去释放已经分配的内存空间。

C++17标准之后,C++标准中还有三种智能指针:shared_ptr、unique_ptr、weak_ptr。下面我们将一一介绍。

RAII机制介绍

RAIIResource Acquisition Is Initialization的简称,其翻译过来就是“资源获取即初始化”,即在构造函数中申请分配资源,在析构函数中释放资源,它是C++语言中的一种管理资源、避免泄漏的良好方法。

C++语言的机制保证了,当创建一个类对象时,会自动调用构造函数,当对象超出作用域时会自动调用析构函数。RAII正是利用这种机制,利用类来管理资源,将资源与类对象的生命周期绑定即在对象创建时获取对应的资源,在对象生命周期内控制对资源的访问,使之始终保持有效,最后在对象析构时,释放所获取的资源。

RAII技术被认为是C++中管理资源的最佳方法,更进一步来说,使用RAII技术也可以实现安全、简洁的状态管理。

智能指针雏形

智能指针雏形代码:

template < typename T>
class SmallSmartPtr {
public: 
 explicit SmallSmartPtr(T* ptr = nullptr)
  : ptr_(ptr) 
 {} 
 ~SmallSmartPtr()
 { 
  delete ptr_; 
  }

 T* get() const { return ptr_; }
private:
 T* ptr_;
};

测试代码:

class Test
{
public:
 Test()
 {
  cout << "Test()" << endl;
 }

 ~Test()
 {
  cout << "~Test()" << endl;
 }
};

int main()
{
  {
   SmallSmartPtr<Test> t(new Test());
 }
 system("pause");
}

测试结果:

shared_ptr原理介绍

shared_ptr是智能指针的一种,不仅通过RAII机制来管理内存资源,还引入了引用计数来解决当多个智能指针指向同一块内存空间的时候,何时释放这块内存空间的问题。也就是说,同一时刻可以有多个shared_ptr拥有一块内存空间的所有权,当最后一个shared_ptr被销毁时,这块内存空间的引用计数为0时,这块内存空间将被释放。

shared_ptr对象有两个指针,一个是指向管理的内存空间,一个是指向内存控制块,内存控制块中包含引用计数和其他的一些信息(删除器和分配器)。

代码示例:

shared_ptr<Object> t1(new Object());
shared_ptr<Object> t2 = t1;

用图表示如下:

t1释放的时候,引用计数减一,然后释放t1的内存空间,如下:

当t2释放的时候,引用计数会再减一,这时引用计数就会变成0,这时就会释放Object的内存空间和内存控制块的空间,同时t2对象的空间也会被释放。

shared_ptr使用方法

1)初始化

//通过构造函数初始化
shared_ptr<Test> t(new Test());

//使用make_shared来初始化智能指针
shared_ptr<Test> t = make_shared<Test>();

注意:不要使用裸指针进行初始化?如:

//尽量不要使用裸指针来初始化智能指针
Test* pTest = new Test();
shared_ptr<Test> t(pTest);

因为使用裸指针初始化智能指针,容易导致多次使用同一个裸指针对多个智能对象进行初始化。这样就会导致两个智能指针在销毁的时候会去释放同一片内存空间。会造成程序异常崩溃。 如:

Test* pTest = new Test();
shared_ptr<Test> t(pTest);
    
//t1释放的时候会导致程序异常
shared_ptr<Test> t1(pTest);

2)支持拷贝构造、赋值

shared_ptr<Test> t(new Test());
shared_ptr<Test> t1(t);
t1 = t;

3)获取原始指针

shared_ptr<Test> t = make_shared<Test>();
Test* pTest = t.get();

unique_ptr

unique_ptr是独占型智能指针。独占性,就是不允许多个智能指针指向同一块内存空间。也不支持拷贝、赋值,即不能通过赋值将一个unique_ptr赋值给另一个unique_ptr。

1)初始化

//通过构造函数初始化
unique_ptr<Test> t(new Test());

//使用make_unique来初始化智能指针
unique_ptr<Test> t = make_unique<Test>();

2)获取原始指针

unique_ptr<Test> t(new Test());
Test* pTest = t.get();

3)支持所有权转移

unique_ptr不支持拷贝、赋值,但支持所有权转移,即智能指针将当前所指的内存空间的所有权交给另一个智能指针。被管理的内存空间永远只有一个智能指针指向它。

unique_ptr<Test> t(new Test());
unique_ptr<Test> t1 = move(t);

weak_ptr

weak_ptr和shared_ptr、unique_ptr不同,weak_ptr不能单独作为智能指针使用。weak_ptr是用来辅助shared_ptr来解决循环引用的问题。

先看一个示例:

class Test;
class Object
{
public:
 Object()
 {
  cout << "Object()" << endl;
 }

 ~Object()
 {
  cout << "~Object()" << endl;
 }

 shared_ptr<Test> m_pTest;
};

class Test
{
public:
 Test()
 {
  cout << "Test()" << endl;
 }

 ~Test()
 {
  cout << "~Test()" << endl;
 }

 shared_ptr<Object> m_pObject;
};

int main()
{
 {
  shared_ptr<Object> t1(new Object());
  shared_ptr<Test> t2(new Test());
  t1->m_pTest = t2;
  t2->m_pObject = t1;
 }//离开作用域,释放t1、t2
 system("pause");
}

执行结果如下:

发现问题没?为什么没有调用两个对象的析构函数?,也就是说,两个智能指针释放了,但是管理的内存空间没有被释放,那不就是内存泄漏吗,我们看下这一切是怎么发生的:

这个示例的重点是,Test和Object对象中各有一个智能指针类型的成员,而且这两个智能指针指向的都是对方的内存空间。

如图所示,每块内存空间都有两个智能指针指向。当t1释放的时候,释放t1对象的内存空间,Test的内存块引用计数减一:

同理,t2对象释放的时候,释放t2对象的内存空间,Object的内存块引用计数减一:

此时,已经没有智能指针管理这块内存空间,但是这两块内存空间还未被释放,这个时候,就造成了内存泄漏。

问题的根因是,Test和Object内部的成员指针指向对方的时候,也造成了引用计数加1。

要解决这个问题就需要用到两个东西:weak_ptr和弱引用计数

也就是说,内存控制块中有一个特殊的引用计数,叫弱引用计数。就是上图中的weak count。为区分,也为了方便表达,我们把原来的引用计数叫强引用计数 。

weak_ptr指向一个对象,不会造成这个对象的内存控制块中的强引用计数加1,只会让弱引用计数加1。而内存空间什么时候释放,是取决于对应的强引用计数什么时候变成0。所以,当t1、t2都被释放之后,两个对象的强引用计数都会变成0。所以,内存空间会被释放。

这个示例的重点是,Test和Object对象中各有一个智能指针类型的成员,而且这两个智能指针指向的都是对方的内存空间。

class Test;
class Object
{
public:
 Object()
 {
  cout << "Object()" << endl;
 }

 ~Object()
 {
  cout << "~Object()" << endl;
 }
//修改成weak_ptr
 weak_ptr<Test> m_pTest;
};

class Test
{
public:
 Test()
 {
  cout << "Test()" << endl;
 }

 ~Test()
 {
  cout << "~Test()" << endl;
 }
//修改成weak_ptr
 weak_ptr<Object> m_pObject;
};

int main()
{
 {
  shared_ptr<Object> t1(new Object());
  shared_ptr<Test> t2(new Test());
  t1->m_pTest = t2;
  t2->m_pObject = t1;
 }
 system("pause");
}

运行结果:

  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2022-04-29 11:55:13  更:2022-04-29 11:55:58 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/20 21:17:25-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码