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

【C++】智能指针的原理和实现

目录

不要用auto_ptr

智能指针的原理和实现

智能指针的实现主要依赖于两个技术

实现智能指针版本1

实现智能指针版本2

引用计数技术的智能指针v3版本:

四种智能指针


不要用auto_ptr

回顾:

C++11 4种智能指针种,C++STL  auto_ptr 不提倡用的原因是:

auto_ptr<TYPE> A,B;
A = B;

第二行执行完毕后,B的地址为0;

这就是智能指针auto_ptr是转移语义造成的,容易让人用错的地方。

智能指针的原理和实现

智能指针的实现主要依赖于两个技术

C++中智能指针的实现主要依赖于两个技术概念:

1、析构函数,对象被销毁时会被调用的一个函数,对于基于栈的对象而言,如果对象离开其作用域则对象会被自动销毁,而此时析构函数也自动会被调用。2、引用计数技术,维护一个计数器用于追踪资源(如内存)的被引用数,当资源被引用时,计数器值加1,当资源被解引用时,计算器值减1。3、操作符重载。
智能指针的大致实现原理就是在析构函数中,检查所引用对象的引用计数,如果引用计数为0,则真正释放该对象内存。

我们是怎么创建原生指针的:

// 创建一个不知所指的指针
char *punknown ;
// 创建空指针
char *pnull = NULL;
// 创建字符串指针
const char *pstr = "Hello world!";
// 创建指向具体类的指针
SomeClass *pclass = new SomeClass();
// 从同类型指针创建指针
SomeClass *pclassother = pclass;

求我们的智能指针类,也能满足对应的用法:

//  创建一个不知所指的智能指针
SmartPointer spunknown;
// 创建空智能指针
SmartPointer spnull = NULL;
// 创建字符串的智能指针
SmartPointer spstr = "Hello world!";
// 创建指向具体类的智能指针
SmartPointer spclass = new SomeClass();

实现智能指针版本1

/*
* file name : smartpointer.h
* desp : 智能指针版本v1
*/
#ifndef __SMARTPOINTER_H__
#define __SMARTPOINTER_H__

template <typename T>  // 将智能指针类定义成模板类
class SmartPointer
{
public:
    // 默认构造函数
    SmartPointer(): mPointer(NULL)
    {
        std::cout << "create unknown smart pointer." << std::endl;

    }
    // 接收不同指针类型的构造函数
    SmartPointer(T *p): mPointer(p)
    {
        std::cout << "create smart pointer at " << static_cast<const void *>(p) << std::endl;
    }
    // 析构函数
    ~SmartPointer()
    {
        std::cout << "release smart pointer at " << static_cast<const void *>(mPointer) << std::endl;
        // 实现内存资源自动销毁机制
        if (mPointer) delete mPointer;
    }
private:
    T *mPointer; // 指向智能指针实际对应的内存资源,根据参数自动推导规则,定义内部资源指针类型
};
#endif // __SMARTPOINTER_H__

上面的智能指针没有实现赋值和拷贝,

默认情况下C++编译器会为我们定义的类生成拷贝构造函数和赋值操作符的实现,但是对于我们的智能指针而言,使用系统默认为我们生成的赋值操作符的实现,是会有问题的。

拷贝构造函数和赋值构造函数的区别:

1)会调用拷贝构造函数的例子:

CExample A;  
 CExample B=A; //把B初始化为A的副本

2)会调用赋值构造函数的例子:

CExample A;  
 CExample C; 
 C = A; //现在需要一个对象赋值操作,被赋值对象的原内容被清除,并用右边对象的内容填充。

1)中“ CExample B=A;  ”语句中的 "=" 在对象声明语句中,表示初始化。更多时候,这种初始化也可用括号表示。 

例CExample B(A); 

2)中,"=" 表示赋值操作。将对象 A 的内容复制到对象C;,这其中涉及到对象C 原有内容的丢弃,新内容的复制。

智能指针类中,有我们自己申请的内存,所以需要自己定义拷贝构造函数和赋值构造函数(完成深拷贝)

2.2 定义构造函数和赋值操作符的函数原型

实现智能指针版本2

/* 
* file name : smartpointer.h
* desp : 智能指针版本v2
*/
#ifndef __SMARTPOINTER_H__
#define __SMARTPOINTER_H__

template <typename T>  // 将智能指针类定义成模板类
class SmartPointer {
public:
    // 默认构造函数
    SmartPointer():mPointer(NULL) {std::cout <<"Create null smart pointer."<< std::endl;}    
    // 接收不同指针类型的构造函数
    SmartPointer(T *p):mPointer(p) {std::cout <<"Create smart pointer at "<<static_cast<const void*>(p)<<std::endl;}     
    // 析构函数
    ~SmartPointer(){
        std::cout << "Release smart pointer at "<<static_cast<const void*>(mPointer)<<std::endl;
        // 实现内存资源自动销毁机制
        if (mPointer) delete mPointer;
    }
    // 拷贝构造函数
    SmartPointer(const SmartPointer &other):mPointer(other.mPointer) {
        std::cout <<"Copy smart pointer at "<<static_cast<const void*>(other.mPointer)<<std::endl;
    }     
   // 赋值操作符                
   SmartPointer &operator = (const SmartPointer &other) {
        // 处理自我赋值的情况
        if (this == &other) return *this;
        // 处理底层指针的释放问题
        if (mPointer) delete mPointer;
        mPointer = other.mPointer;  
        std::cout <<"Assign smart pointer at "<<static_cast<const void*>(other.mPointer)<<std::endl;
        return *this;
   } 

private:
    T *mPointer; // 指向智能指针实际对应的内存资源,根据参数自动推导规则,定义内部资源指针类型
};
#endif // __SMARTPOINTER_H__

还是存在一些问题:就是当有多个智能指针执行同一块底层资源,在释放时,每个指针都会去释放一次底层资源,这就造成了最后的 double free 错误

æ­¤å¤è¾å¥å¾ççæè¿°

æ­¤å¤è¾å¥å¾ççæè¿°

此时智能指针SmartPointer2将会变成野指针!

所以要引入计数:

引入引用计数的智能指针,其实现主要需要更新以下几点:

1接收不同对象类型的构造函数

这个构造函数实现,比较简单,直接将引用计数加1 即可。

2析构函数

析构函数的实现,不能再直接做delete操作,而是需要先对引用计数减1,当引用计数为0时,才做delete操作。

3拷贝构造函数

拷贝构造函数的实现,底层指针共享,然后将引用计数加1 即可。

4赋值操作符

赋值操作的实现,稍微复杂一些,涉及到将新指向对象的引用计数加1,将原指向对象的引用计数减1,如果有需要还要销毁原指向对象。这里有一点值得注意的地方,我们新的赋值操作的实现,不再需要if (this == &other) return *this;语句处理自我赋值的情况,读者可自行分析一下我们新的赋值操作的实现为何不需要通过if语句去处理自我赋值的情况。

为了能够使用引用计数技术,我们的智能指针不能再像原生指针那样能用可以指向任意资源对象,我们的智能指针只能指向实现了存在方法incRefCount和方法decRefCount的资源类了。

引用计数技术的智能指针v3版本:

/* 
* file name : smartpointer.h
* desp : 智能指针版本v3
*/
#ifndef __SMARTPOINTER_H__
#define __SMARTPOINTER_H__

template <typename T>  // 将智能指针类定义成模板类
class SmartPointer {
public:
    // 默认构造函数
    SmartPointer():mPointer(NULL) {std::cout <<"Create null smart pointer."<< std::endl;}    
    // 接收不同对象类型的构造函数
    SmartPointer(T *p):mPointer(p) {
        std::cout <<"Create smart pointer at "<<static_cast<const void*>(p)<<std::endl;
        /*智能指针指向类T,引用计数加1*/
        if (mPointer) mPointer->incRefCount();
    }     
    // 析构函数
    ~SmartPointer(){
        std::cout << "Release smart pointer at "<<static_cast<const void*>(mPointer)<<std::endl;
        // 实现内存资源自动销毁机制
        if (mPointer && mPointer->decRefCount()==0) delete mPointer;
    }
    // 拷贝构造函数
    SmartPointer(const SmartPointer &other):mPointer(other.mPointer) {
        std::cout <<"Copy smart pointer at "<<static_cast<const void*>(other.mPointer)<<std::endl;
       // 引用计数加1
       if(mPointer) mPointer->incRefCount();
    }     
   // 赋值操作符         
   SmartPointer &operator = (const SmartPointer &other) {
       T *temp(other.mPointer);
       // 新指向对象,引用计数值加1
       if (temp) temp->incRefCount();
       // 原指向对象,引用计数值减1,如果减1后引用计数为0 销毁原资源对象
       if (mPointer && mPointer->decRefCount()==0) delete mPointer;
       // 智能指针指向新资源对象
       mPointer = temp;  
       return *this;
   } 

private:
    T *mPointer; // 指向智能指针实际对应的内存资源,根据参数自动推导规则,定义内部资源指针类型
};

/*引用计数基类*/
class RefBase   
{   
    public:   
        RefBase() : mCount(0){ }   
        void incRefCount(){   
            mCount++;   
        }   
        int decRefCount(){   
            return --mCount;
        }   
        // 调试接口,返回对象当前引用计数   
        int getRefCount(){   
            return mCount;   
        }     
        virtual ~RefBase(){ }
    private:   
        int mCount;   
};   
#endif // __SMARTPOINTER_H__


测试代码(sptestcase3.cpp):


/* 
* file name : sptestcase3.cpp
* desp : 智能指针测试代码 case3 测试智能指针的引用计数功能
*/

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

/*继承于引用计数基类的SomeClass类*/
class SomeClass: public RefBase{
public:
    SomeClass(){std::cout << "SomeClass default constructor !"<<std::endl;}
    ~SomeClass(){std::cout << "SomeClass deconstructor !"<<std::endl;}
};

void testcase3(void)
{
    SomeClass *pSomeClass =  new SomeClass(); //1
    SmartPointer<SomeClass> spOuter = pSomeClass;
    std::cout << "SomeClass Ref Count (" << pSomeClass->getRefCount() << ") outer 1."<< std::endl;
    { // inner 语句块
          SmartPointer<SomeClass> spInner = spOuter;
          std::cout << "SomeClass Ref Count (" << pSomeClass->getRefCount() << ") inner."<< std::endl;
    }
    std::cout << "SomeClass Ref Count (" << pSomeClass->getRefCount() << ") outer 2."<< std::endl;
    // delete pSomeClass ; 不需要也不能执行delete操作!

    std::cout << "new another SomeClass class for spOuter."<< std::endl;
    SmartPointer<SomeClass> spOuter2 = new SomeClass();
    spOuter = spOuter2;// 1处new出来的SomeClass将会被自动释放  
}

int main(void)
{
    testcase3();
    return 0;
}

还不够完美,我们使用指针指代资源对象,最终还是需要去访问资源对象的内容,那么智能指针如何满足这个需求呢?

2.1 解引用

我们使用指针最终还是需要通过指针去访问其所指向的资源对象的数据及方法,这个过程称为指针的解引用过程。指针的解引用可以使用*运算符和->运算符;

我们需要重载*运算符和->运算符:

template <typename T>  
class SmartPointer {
public:
    /*重载运算符* */
    T&  operator* () const;
    /*重载运算符-> */
    T* operator-> () const;
};

在我们的智能指针类重载了运算符后,当我们这样使用智能指针时:

(*SmartPointer).func();
SmartPointer->func();

其实等价于:

(*(SmartPointer.operator*())).func();
(SmartPointer.operator->())->func();

也就是说,我们在重载*运算符时其返回的应该是智能指针所指的资源对象实体, 而->运算符则应该返回的是智能指针指针所指的资源对象的内存地址,既是我们的实现是这样的:

// 重载*和-> 运算符
    /*重载运算符* */
    T&  operator* () const {return *mPointer;};
    /*重载运算符-> */
    T* operator-> () const {return mPointer;};

比较和判空:

未学习…… 

四种智能指针

https://blog.csdn.net/bandaoyu/article/details/84427789

相关文章:

  • 【C++容器】数组和vector、array三者区别和联系
  • 【init和selft】Python中__init__和self的意义和作用
  • 【多线程】Linux下c语言多线程编程---学习
  • 【python】Series和DataFrame的简单介绍
  • 【闭包】Python中的闭包
  • 【线程池】C语言实现的简单的线程池
  • 【互斥锁和条件变量】何时互斥锁不够,还需要条件变量?
  • 【malloc和calloc】malloc和calloc函数区别
  • 【pthread_detach/pthread_join】pthread_detach()与pthread_join的区别?
  • 【时间复杂度】时间复杂度
  • 【Dll调试】DLL调试方法
  • 【机器学习】线性回归数学推导
  • 【VS消除警告】VS消除特定警告/安全函数警告C4996 strncpy unsafe……
  • CSDN 博客备份工具
  • 【FTP】linux FTP传文件到windows
  • 《网管员必读——网络组建》(第2版)电子课件下载
  • 「译」Node.js Streams 基础
  • Computed property XXX was assigned to but it has no setter
  • ES6之路之模块详解
  • es的写入过程
  • markdown编辑器简评
  • October CMS - 快速入门 9 Images And Galleries
  • Promise面试题,控制异步流程
  • Promise面试题2实现异步串行执行
  • Python - 闭包Closure
  • Python代码面试必读 - Data Structures and Algorithms in Python
  • springboot_database项目介绍
  • vue-router的history模式发布配置
  • webpack入门学习手记(二)
  • 聊聊hikari连接池的leakDetectionThreshold
  • 译自由幺半群
  • 再谈express与koa的对比
  • 昨天1024程序员节,我故意写了个死循环~
  • ​LeetCode解法汇总2808. 使循环数组所有元素相等的最少秒数
  • "无招胜有招"nbsp;史上最全的互…
  • #android不同版本废弃api,新api。
  • (每日持续更新)jdk api之StringBufferInputStream基础、应用、实战
  • (生成器)yield与(迭代器)generator
  • (四)模仿学习-完成后台管理页面查询
  • .NET 6 在已知拓扑路径的情况下使用 Dijkstra,A*算法搜索最短路径
  • .NET Core/Framework 创建委托以大幅度提高反射调用的性能
  • .Net(C#)自定义WinForm控件之小结篇
  • .NET(C#、VB)APP开发——Smobiler平台控件介绍:Bluetooth组件
  • [Android]通过PhoneLookup读取所有电话号码
  • [C++][基础]1_变量、常量和基本类型
  • [CSAWQual 2019]Web_Unagi ---不会编程的崽
  • [LeetCode][LCR178]训练计划 VI——使用位运算寻找数组中不同的数字
  • [MYSQL]mysql将两个表结果合并到一起
  • [node]Node.js 模块系统
  • [python]基本输出输入函数
  • [Python学习笔记][Python内置函数]
  • [RK3568][Android12.0]--- 系统自带预置第三方APK方法
  • [ssh]如何设计ARM板上多用户key登录系统
  • [svc]ssh+gg二步认证
  • [USACO5.5]Hidden Password