c++ 学习系列 -- 智能指针

一   为什么引入智能指针?解决了什么问题?

C++ 程序设计中使用堆内存是非常频繁的操作,堆内存的申请和释放都由程序员自己管理。但使用普通指针,容易造成内存泄露(忘记释放)、二次释放、程序发生异常时内存泄露等问题等。

另外,使用普通指针容易产生 野指针、悬空指针 等问题。

所以 C++11 就引入了智能指针来管理内存。

二  常用的智能指针与区别

常用智能指针有  shared_ptrunique_ptr weak_ptr 

  • unique_ptr: 独占式指针,同一时刻只允许有一个 unique_ptr 指针指向一个对象
  • shared_ptr: 共享式指针,同一时刻允许多个 shared_ptr 指针指向同一个对象。
    •  缺点:出现相互引用时,容易导致死锁或者内存无法释放内存的问题
  • weak_ptr: 为了解决 shared_ptr 相互引用可能导致的死锁或无法释放内存的问题而引入,通常与shared_ptr 配合使用。但是由于缺少引用计数,一旦最后一个指向对象的shared_ptr被销毁,对象就会被释放。即使有weak_ptr指向对象,对象也还是会被释放。 C++11智能指针(weak_ptr) - 简书 (jianshu.com)

三  常用智能指针使用例子

   1  unique_ptr 例子

        unique_ptr 是一个独享所有权的智能指针

#include<memory>
#include<string>
#include<iostream>

class Person
{
public:
    Person(std::string name):m_name(name)
    {
        std::cout << "Person constructor name: " << m_name << std::endl;
    }

    ~Person()
    {
        std::cout << "Person destructor name: " << m_name << std::endl;
    }

private:
    std::string m_name;
};

#include<memory>



void testUniquePtr()
{
    std::unique_ptr<Person>  p1_ptr(new Person("P1 ---"));
    std::unique_ptr<Person>  p2_ptr = std::move(p1_ptr);

   // std::unique_ptr<Person>  p3_ptr = p2_ptr; // 编译不过
   // std::unique_ptr<Person>  p4_ptr(p2_ptr); // 编译不过

}


int main(int argc, char *argv[])
{
    testUniquePtr();
    return 0;
}

   输出

2   shared_ptr 例子

#include<memory>
#include<iostream>
#include<string>

using namespace std;

void testSharedPtr()
{
        shared_ptr<string> pa(new string("PAAAA"));
        shared_ptr<string> pb(new string("PBBBB"));
        cout << "*pa " << *pa << endl;//CHN
        cout << "pa.use_count " << pa.use_count() << endl;//1
        cout << "*pb " << *pb << endl;//USA
        cout << "pb.use_count " << pb.use_count() << endl;//1

        pa = pb;
        cout << *pa << endl;//USA
        cout << "pa.use_count " << pa.use_count() << endl;//2:pa和pb指向同一个资源USA了,该资源的计数为2,所以pb、pb都输出2
        cout << "pb.use_count " << pb.use_count() << endl;//2

        pa.reset();
        pb.reset();
        cout << "pa.use_count " << pa.use_count() << endl;//0
        cout << "pb.use_count " << pb.use_count() << endl;//0
}

int main(int argc, char *argv[])
{

    testSharedPtr();
    return 0;
}

   3  weak_ptr 例子

       3.1  shared_ptr 相互引用的问题

// a.h
#include<memory>

class B;

class A
{
public:
    A()
   {
       std::cout << "A constructor ---" << std::endl;
   }

    ~A()
   {
    std::cout << "A destructor ---" << std::endl;
   }

public:
    std::shared_ptr<B> m_b_ptr;
};

// b.h
#include<memory>

class A;

class B
{
public:
    B()
    {
       std::cout << "A constructor ---" << std::endl;
   }
    ~B()
    {
       std::cout << "A destructor ---" << std::endl;
   }

public:
    std::shared_ptr<A> m_a_ptr;
};


// main.cpp
void testSharedPtr()
{
    std::shared_ptr<A> pa(new A);
    cout << "pa.use_count " << pa.use_count() << endl;//1

    std::shared_ptr<B> pb(new B);
    cout << "pb.use_count " << pb.use_count() << endl;//1

    pa->m_b_ptr = pb;
    cout << "pb.use_count " << pb.use_count() << endl;//2
    cout << "pa.use_count " << pa.use_count() << endl;//1
    pb->m_a_ptr = pa;
    //由于share_ptr是共享资源,所以pb所指向的资源的引用计数也会加1
    cout << "pb.use_count " << pb.use_count() << endl;//2
    cout << "pa.use_count " << pa.use_count() << endl;//2

}

int main(int argc, char *argv[])
{
    testSharedPtr();

    return 0;
}


  输出 :     

通过输出可以看到未执行析构函数,存在内存泄漏。

引用计数分别增加到了 2 ,不为 0 就意味着无法释放内存。

3.2  weak_ptr 与 share_ptr 使用 

// a.h
#include<memory>

class B;

class A
{
public:
    A()
   {
       std::cout << "A constructor ---" << std::endl;
   }

    ~A()
   {
    std::cout << "A destructor ---" << std::endl;
   }

public:
    std::weak_ptr<B> m_b_ptr;
};

// b.h
#include<memory>

class A;

class B
{
public:
    B()
    {
       std::cout << "A constructor ---" << std::endl;
   }
    ~B()
    {
       std::cout << "A destructor ---" << std::endl;
   }

public:
    std::shared_ptr<A> m_a_ptr;
};


// main.cpp
void testWeakPtr()
{
 std::shared_ptr<A> pa(new A);
    cout << "pa.use_count " << pa.use_count() << endl;//1

    std::shared_ptr<B> pb(new B);
    cout << "pb.use_count " << pb.use_count() << endl;//1

    pa->m_b_ptr = pb;
    cout << "pb.use_count " << pb.use_count() << endl;//1
    cout << "pa.use_count " << pa.use_count() << endl;//2
    pb->m_a_ptr = pa;
    
    cout << "pb.use_count " << pb.use_count() << endl;//1  由于 weak_ptr 是弱引用,不会增加引用计数
    cout << "pa.use_count " << pa.use_count() << endl;//2  由于share_ptr是共享资源,所以pb所指向的资源的引用计数也会加1


}

int main(int argc, char *argv[])
{
    testWeakPtr();

    return 0;
}


输出:

 通过输出可以看到执行析构函数,不存在内存泄漏。

//  资源B的引用计数一直就只有1,当pb析构时,B的计数减一,变为0,B得到释放,
//  B释放的同时也会使A的计数减一,同时pa自己析构时也会使资源A的计数减一,那么A的计数为0,A得到释放。

四  智能指针的原理与简单实现

智能指针实际运用的就是c++ 中的 RAII 技术,详情见  C++ 学习系列 二 -- RAII 机制_在河之洲木水的博客-CSDN博客

1. unique_ptr 

 因为是独占型指针,不可以拷贝与赋值,所以需要禁止拷贝构造函数与赋值函数

// my_nuique_ptr.h
template<typename T>
class my_unique_ptr
{
public:
    my_unique_ptr(T* ptr = nullptr);
    ~my_unique_ptr();
    my_unique_ptr(my_unique_ptr&& other_ptr); // c++ 中声明移动构造函数后,则自动禁用拷贝构造函数
    my_unique_ptr& operator=(my_unique_ptr&& other_ptr); // c++ 中声明移动赋值函数后,则自动禁用拷贝赋值函数

    T& operator*() const; // 指针的基本操作,取值
    T* operator->() const;
    operator bool() const; // 提供一个本类型到bool的隐式转换,不允许使用参数

private:
    T* m_ptr;
};

template<typename T>
my_unique_ptr<T>::my_unique_ptr(T* ptr):m_ptr(ptr)
{

}

template<typename T>
my_unique_ptr<T>::~my_unique_ptr()
{
    delete m_ptr;
}

template<typename T>
my_unique_ptr<T>::my_unique_ptr(my_unique_ptr&& other_ptr)
{
    this->m_ptr = other_ptr.m_ptr;
    other_ptr.m_ptr = nullptr;
}

template<typename T>
my_unique_ptr<T>&
my_unique_ptr<T>::operator=(my_unique_ptr&& other_ptr)
{
    this->m_ptr = other_ptr.m_ptr;
    other_ptr.m_ptr = nullptr;

    return this;
}

template<typename T>
T&   my_unique_ptr<T>::operator*() const
{
    return *m_ptr;
}

template<typename T>
T* my_unique_ptr<T>::operator->() const
{
    return m_ptr;
}

template<typename T>
my_unique_ptr<T>::operator bool() const
{
    return m_ptr;
}



// main.cpp

#include<iostream>
#include"my_unique_ptr.h"

void testFunc2()
{
    my_unique_ptr<Person> my_ptr(new Person("p1 ------"));
    std::cout << "person name1: "<<my_ptr->getName() << std::endl;
    my_unique_ptr<Person> my_ptr2(std::move(my_ptr));
    std::cout << "person name2: "<<my_ptr2->getName() << std::endl;

     //my_unique_ptr<Person> my_ptr3 = my_ptr; // 编译失败
    // my_unique_ptr<Person> my_ptr4(my_ptr); // 编译失败

}


int main()
{
   testFunc2();

   return 0;
}

输出:

2. shared_ptr

shared_ptr 是共享型指针,同一时刻可以右多个指针指向同一个对象,只有最后一个指针离开作用域时,才会调用对象的析构函数,释放对象中的资源。

那么是如何实现的呢?

答案是:利用引用计数法。在 类 shared_ptr 中定义一个成员变量引用计数 share_count ,当有一个指针指向相同的对象时,就将 share_count 就自增 1,为了各 shared_ptr 的引用计数 share_count 同时增加,可以将 share_count 手动开辟一个空间,用普通指针指向它。

若是考虑到多线程的场景,还应该将 引用计数 share_count 加上锁才可以。

// my_shared_ptr.h
#include <mutex>
static std::mutex gMutex;


template<typename T>
class my_shared_ptr
{
public:
    my_shared_ptr(T* ptr = nullptr);
    ~my_shared_ptr();
    my_shared_ptr(my_shared_ptr& other_ptr);
    my_shared_ptr& operator=(my_shared_ptr& other_ptr);

    T& operator*();
    T* operator->();

    int user_count();

private:

    void addCount();
    void minusCount();

    T* m_ptr;
    int* share_count = nullptr;
};


template<typename T>
my_shared_ptr<T>::my_shared_ptr(T* ptr):m_ptr(ptr)
{
    if(!share_count)
    {
        share_count = new int(1);
    }
}

template<typename T>
my_shared_ptr<T>::~my_shared_ptr()
{
    minusCount();
    if((*this->share_count) == 0 && m_ptr)
        delete m_ptr;
}

template<typename T>
my_shared_ptr<T>::my_shared_ptr(my_shared_ptr& other_ptr)
{
    this->m_ptr = other_ptr.m_ptr;
    this->share_count = other_ptr.share_count;
    addCount();
}

template<typename T>
my_shared_ptr<T>& my_shared_ptr<T>::operator=(my_shared_ptr& other_ptr)
{
    this->m_ptr = other_ptr.m_ptr;
    this->share_count = other_ptr.share_count;
    addCount();
    return *this;
}

template<typename T>
T&  my_shared_ptr<T>::operator*()
{
    return *this->m_ptr;
}

template<typename T>
T*  my_shared_ptr<T>::operator->()
{
    return this->m_ptr;
}

template<typename T>
void my_shared_ptr<T>::addCount()
{
     std::lock_guard<std::mutex> guard(gMutex);
    (*this->share_count)++;
}

template<typename T>
void my_shared_ptr<T>::minusCount()
{
     std::lock_guard<std::mutex> guard(gMutex);
    (*this->share_count)--;
}

template<typename T>
int my_shared_ptr<T>::user_count()
{
    return *this->share_count;
}


// person.h
#include<string>

class Person
{
public:
    Person(std::string name);
    Person(const Person& p);
    ~Person();

    std::string& getName();

private:
    std::string m_name;
};


// person.cpp
#include "person.h"
#include<iostream>
Person::Person(std::string name):m_name(name)
{
    std::cout << "Person constructor name: " << m_name << std::endl;
}

Person::Person(const Person& p)
{
    this->m_name = p.m_name;
    std::cout << "Person  copy constructor name: " << this->m_name << std::endl;
}

Person::~Person()
{
    std::cout << "Person destructor name: " << m_name << std::endl;
}

std::string& Person::getName()
{
    return m_name;
}




// main.cpp

void  testMySharedPtr()
{
    my_shared_ptr<Person>  ptr1(new Person("ptr1 ---"));
    std::cout << "ptr1 user_count: " << ptr1.user_count() << std::endl;

    my_shared_ptr<Person> ptr2(ptr1);
    std::cout << "ptr1 user_count: " << ptr1.user_count() << std::endl;
    std::cout << "ptr2 user_count: " << ptr2.user_count() << std::endl;

    my_shared_ptr<Person> ptr3 = ptr2;
    std::cout << "ptr1 user_count: " << ptr1.user_count() << std::endl;
    std::cout << "ptr2 user_count: " << ptr2.user_count() << std::endl;
    std::cout << "ptr3 user_count: " << ptr3.user_count() << std::endl;

}

int main()
{
    testMySharedPtr();

    return 0;
}

输出

 

3. weak_ptr

前面提到过,weak_ptr 是与 shared_ptr 配合使用的,weak_ptr 无引用计数。

// my_weak_ptr.h
template<typename T>
class my_weak_ptr
{
public:
    my_weak_ptr(T* ptr = nullptr);
    ~my_weak_ptr();
    my_weak_ptr(my_weak_ptr& other_ptr);
    my_weak_ptr& operator=(my_weak_ptr& other_ptr);

    T& operator*();
    T* operator->();

private:

    T* m_ptr;
};


template<typename T>
my_weak_ptr<T>::my_weak_ptr(T* ptr):m_ptr(ptr)
{

}

template<typename T>
my_weak_ptr<T>::~my_weak_ptr()
{
    if(m_ptr)
        delete m_ptr;
}

template<typename T>
my_weak_ptr<T>::my_weak_ptr(my_weak_ptr& other_ptr)
{
    this->m_ptr = other_ptr.m_ptr;
}

template<typename T>
my_weak_ptr<T>& my_weak_ptr<T>::operator=(my_weak_ptr& other_ptr)
{
    this->m_ptr = other_ptr.m_ptr;
    return *this;
}

template<typename T>
T&  my_weak_ptr<T>::operator*()
{
    return *this->m_ptr;
}

template<typename T>
T*  my_weak_ptr<T>::operator->()
{
    return this->m_ptr;
}

// A.h
#include"my_weak_ptr.h"

class B;

class A
{
public:
    A();
    ~A();

public:
    my_weak_ptr<B> m_b_ptr;
};


// A.cpp
A::A()
{
    std::cout << "A constructor ---" << std::endl;

}

A::~A()
{
    std::cout << "A destructor ---" << std::endl;
}

// B.h
#include"my_shared_ptr.h"

class A;

class B
{
public:
    B();
    ~B();

public:
    my_shared_ptr<A> m_a_ptr;
};


// B.cpp
#include "b.h"
#include<iostream>
B::B()
{
    std::cout << "B constructor ---" << std::endl;

}

B::~B()
{
    std::cout << "B destructor -- " << std::endl;
}



// main.cpp

void testMyWeakPtr()
{
    my_shared_ptr<A> pa(new A);

    my_weak_ptr<B> pb(new B);

    pa->m_b_ptr = pb;
    pb->m_a_ptr = pa;

}

int main()
{

    testMyWeakPtr();

    return 0;
}

输出:

忽视其中 B 析构了两次,通过结果可以看到,A 与 B 均能够析构。

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/76310.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

MySQL和Redis如何保证数据一致性

MySQL与Redis都是常用的数据存储和缓存系统。为了提高应用程序的性能和可伸缩性&#xff0c;很多应用程序将MySQL和Redis一起使用&#xff0c;其中MySQL作为主要的持久存储&#xff0c;而Redis作为主要的缓存。在这种情况下&#xff0c;应用程序需要确保MySQL和Redis中的数据是…

Java项目作业~ 通过html+Servlet+MyBatis,完成站点信息的添加功能

需求&#xff1a; 通过htmlServletMyBatis&#xff0c;完成站点信息的添加功能。 以下是站点表的建表语句&#xff1a; CREATE TABLE websites (id int(11) NOT NULL AUTO_INCREMENT,name char(20) NOT NULL DEFAULT COMMENT 站点名称,url varchar(255) NOT NULL DEFAULT ,…

系统架构设计专业技能 · 软件工程之软件测试与维护(六)【系统架构设计师】

系列文章目录 系统架构设计专业技能 网络规划与设计&#xff08;三&#xff09;【系统架构设计师】 系统架构设计专业技能 系统安全分析与设计&#xff08;四&#xff09;【系统架构设计师】 系统架构设计高级技能 软件架构设计&#xff08;一&#xff09;【系统架构设计师…

数据统计与可视化的Dash应用程序

在数据分析和可视化领域&#xff0c;Dash是一个强大的工具&#xff0c;它结合了Python中的数据处理库&#xff08;如pandas&#xff09;和交互式可视化库&#xff08;如Plotly&#xff09;以及Web应用程序开发框架。本文将介绍如何使用Dash创建一个简单的数据统计和可视化应用程…

FPGA应用学习笔记-----复位电路(二)和小结

不可复位触发器若和可复位触发器混合写的话&#xff0c;不可复位触发器是由可复位触发器馈电的。 不应该出现的复位&#xff0c;因为延时导致了冒险&#xff0c;异步复位存在静态冒险 附加素隐含项&#xff0c;利用数电方法&#xff0c;消除静态冒险 这样多时钟区域还是算异步的…

MYSQL作业二

首先&#xff0c;查询数据库并是选择一个数据库打开 show table; use sys 第二部&#xff0c;创建一个表格 CREATE TABLE employee ( 部门号 int(11) not null, 职工号 int(11) not null, 工作时间 date not null, 工资 float(8,2) not null, 政治面貌 varchar(20) not…

一个完美的自动化测试框架应该怎么写?

一、什么是自动化测试框架&#xff1f; 自动化测试框架是为自动化测试用例或者脚本提供执行环境而搭建的基础设施。自动化测试框架有助于有效地开发、执行和报告自动化测试用例。 优点&#xff1a; 代码复用 提高测试效率 更高的测试覆盖率 维护成本低 更早发现和记…

Spring对象装配

在spring中&#xff0c;Bean的执行流程为启动spring容器&#xff0c;实例化bean&#xff0c;将bean注册到spring容器中&#xff0c;将bean装配到需要的类中。 既然我们需要将bea装配到需要的类中&#xff0c;那么如何实现呢&#xff1f;这篇文章&#xff0c;将来阐述一下如何实…

windows以管理员的身份运行CMD

电脑在装系统的时候&#xff0c;我的用户不是最高权限的管理员。 今天在工作的时候&#xff0c;使用CMD。运行失败&#xff0c;提示我需要使用管理员的身份运行CMD才可以。 使用右键点击左下角的windows图标 选择红框标注的那项。 以普通身份运行&#xff1a; 以管理员身份运行…

HTML5的介绍和基本框架

目录 HTML5 HTML5介绍 HTML5的DOCTYPE声明 HTML5基本骨架 html标签 head标签 body标签 title标签 meta标签 在vscode中写出第一个小框架 HTML5 HTML5介绍 HTML5是用来描述网页的一种语言&#xff0c;被称为超文本标记语言。用HTML5编写的文件&#xff0c;后缀以.ht…

24届近3年上海电力大学自动化考研院校分析

今天给大家带来的是上海电力大学控制考研分析 满满干货&#xff5e;还不快快点赞收藏 一、上海电力大学 学校简介 上海电力大学&#xff08;Shanghai University of Electric Power&#xff09;&#xff0c;位于上海市&#xff0c;是中央与上海市共建、以上海市管理为主的全日…

【vue】简洁优雅的火花线、趋势线

来由 在github发现个好看易用的vue趋势线组件&#xff0c;特此记录。 效果 趋势图生成后效果如上&#xff0c;线条为渐变色&#xff0c;可设置是否平滑。具体线条走势&#xff0c;根据数据动态生成。 使用 安装 npm i vuetrend -S 引入 import Vue from "vue"…

无涯教程-Perl - sprintf函数

描述 此函数使用FORMAT基于LIST中的值返回格式化的字符串。本质上与printf相同,但是返回格式化的字符串而不是将其打印。 语法 以下是此函数的简单语法- sprintf FORMAT, LIST返回值 此函数返回SCALAR(格式化的文本字符串)。 例 以下是显示其基本用法的示例代码- #!/us…

重要日期提醒软件是哪个?帮你记住重要日程的提醒软件

生活、工作、学习中有众多重要的日期&#xff0c;如生日、纪念日、会议、截止日期等&#xff0c;它们承载着我们珍贵的回忆和重要的任务&#xff0c;因此绝对不能忘记。然而在信息爆炸的时代&#xff0c;我们的生活和工作节奏都是非常快的&#xff0c;时常会忘记这些重要的日程…

CentOS 7 下 Keepalived + Nginx 实现双机高可用

CentOS 7 下 Keepalived Nginx 实现双机高可用 文章目录 CentOS 7 下 Keepalived Nginx 实现双机高可用服务器准备服务信息服务架构 服务安装nginxKeepalived 服务配置nginxKeepalived 启动服务nginxkeepalived 服务验证查看 VIP 状态CURL 命令访问浏览器访问 高可用验证停止…

ZDH-wemock模块

本次介绍基于版本v5.1.1 目录 项目源码 预览地址 安装包下载地址 wemock模块 wemock模块前端 配置首页 配置mock wemock服务 下载地址 打包 运行 效果展示 项目源码 zdh_web: https://github.com/zhaoyachao/zdh_web zdh_mock: https://github.com/zhaoyachao/z…

了解51单片机

目录 51单片机名字的由来 主要功能 1.控制处理 2.数据处理 3.通信 4.定时计数 51单片机的组成 1.中央处理器CPU 2.存储器RAM、只读存储器ROM 3.I/O口和中断系统 4.显示驱动电路、A/D转换器 5.定时器/计数器、脉宽调制电路、模拟多路转换器等电路 单片机的应用领域(…

论文阅读:《Waymo Public Road Safety Performance Data》

文章目录 1 背景2 方法2.1 数据来源2.2 碰撞数据 3 碰撞事件分析4 讨论 1 背景 这篇文章是讲waymo道路安全性能数据分析的&#xff0c;主要想表达的是waymo自动驾驶系统在安全上面的出色表现&#xff0c;以向政府、大众提高自己产品的公信力。 这篇文章分析的数据是自从2019年到…

Field injection is not recommended

文章目录 1. 引言2. 不推荐使用Autowired的原因3. Spring提供了三种主要的依赖注入方式3.1. 构造函数注入&#xff08;Constructor Injection&#xff09;3.2. Setter方法注入&#xff08;Setter Injection&#xff09;3.3. 字段注入&#xff08;Field Injection&#xff09; 4…

日志采集分析ELK

这里的 ELK其实对应三种不同组件 1.ElasticSearch&#xff1a;基于Java&#xff0c;一个开源的分布式搜索引擎。 2.LogStash&#xff1a;基于Java&#xff0c;开源的用于收集&#xff0c;分析和存储日志的工具。&#xff08;它和Beats有重叠的功能&#xff0c;Beats出现之后&a…