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++程序设计之智能指针的理论与实现 -> 正文阅读

[C++知识库]C++程序设计之智能指针的理论与实现

目录

一、C++中裸指针的不安全性

1、作为类的成员

2、异常处理

二、智能指针的引入

1、unique_ptr

2、shared_ptr

3、weak_ptr

二、智能指针的简易实现

1、unique_ptr的实现

2、shared_ptr的实现

?总结


????????C++的指针是C++中非常重要的概念,它在堆上为对象分配内存,但是裸指针(普通指针)使用很不安全,处理不当容易变成野指针(指向的内存已释放),造成内存泄露。C++的指针在开发场景中可能经常被多次复制、赋值、移动,例如函数传参、并行场景处理共享对象等。

一、C++中裸指针的不安全性

C++中的裸指针在使用时容易出现不安全的场景,这里简单列举几个常见情景。

1、作为类的成员

假如一个C++类中含有裸指针作为类成员,那么其析构函数和拷贝构造函数必须被重写,必要时也要重载赋值操作符,这样才能保证类A在使用中的安全。

class A{
public:
    A():p(nullptr){}
    A(const int *_p):p(new int(*_p)){}// 拷贝构造函数
    ~A(){if(p)delete p;} // 析构函数
    // 重载=操作符 
    A& operator=(const A &_a){
        if(this==&_a) return *this;
        if(p)delete p;// 将原先的内存释放掉
        this->p=_a.p;// 修改p的执行
        return *this;
    }
private:
    int * p;
}

2、异常处理

void process()
{
    A* a= new A();
    a->do_something(); // 可能会发生异常
    delete a;
}

? ? ? ? 上图是一种很常见的使用形式,在正常流程下会正常执行。但是如果a->do_something()发生了异常,那么delete a将不会被执行。此时就会发生内存泄漏。我们当然可以使用try...catch捕捉异常,在catch里面执行delete,但是这样代码上并不美观,也容易漏写。

二、智能指针的引入

? ? ? 为了更加安全地使用动态内存,C++引入了智能指针的概念。智能指针(pointer-like classes,像指针的类)。智能指针在用法上基本类似普通的C++指针,其重要区别是它负责自动释放所指向的对象。

? ? ? C++98提供了auto_ptr;C++11舍弃了C++98的auto_ptr,在头文件<memory>提供了3个新的智能指针类型,这些智能指针都是在裸指针的基础上封装而来的,对比如下:

对象所有权

执行效率

应用频率

安全性

特点

裸指针

最高

最高

较低

手动delete、复制、拷贝、处理异常等

unique_ptr<T>

专属所有权

较高

较高

较高

不支持复制和赋值;支持移动;

shared_ptr<T>

共享所有权

较低

较低

较高

支持复制、赋值和移动;循环引用时出错

weak_ptr<T>

临时所有权

较低

最低

较高

支持复制、赋值和移动;弥补循环引用

注意:对象所有权是很多编程语言都会涉及的概念,在C++中可以理解成对对象的内存资源的操作权。

? ? ? ? 通过上表我们可以发现3种智能指针各有不同的特点,应用于不同的场景,裸指针的使用则需要考虑多种情况,不够安全,但在实际开发中智能指针不能不加思考地全部使用智能指针处理,常见的理由如下:

1、智能指针具有高传染性,在项目中使用智能指针很可能要替换很多裸指针,风险很大。

2、在复杂场景中,智能指针出现相互引用时则危险性很高。

3、智能指针的开销大概率会比裸指针大。

? ? ? 我们假设存在一个类A,尝试使用以下几个智能指针去展示其用法:

class A{
public:
    A(){cout << "A default constructor !"<<endl;}
    void print(){cout << "call A.print() !"<<endl;}
    ~A(){cout << "A deconstructor !"<<endl;}
};

1、unique_ptr<T>

? ? ? ? unique_ptr是一种具有专属所有权的智能指针。unique_ptr管理的内存只能被一个对象持有,该指针不支持复制和赋值,只支持移动。其内存占用和执行性能和裸指针接近。? unique_ptr作为类成员时,不需要在析构函数中delete;unique_ptr在执行代码抛出异常时,离开作用域也能自行释放内存。

{
   unique_ptr<A> pA(new A());//pA指向A,拥有A的内存资源的所有权
   pA->print();// 访问A的方法
   //(一)不支持拷贝
   //unique_ptr<A> pA1(pA);//编译报错,不支持拷贝
   //(二)不支持赋值
   unique_ptr<A> pA2(new A());//pA指向A,拥有A的内存资源的所有权
   //pA2=pA; // 编译报错,不支持赋值
   //(三)支持移动
   unique_ptr<A> pA3 = std::move(pA);//pA将所有权移交pA3,自己失去所有权
   if(pA == NULL) cout<<"pA = NULL\n";// 
}

?输出结果如下:

2、shared_ptr<T>

? ? ? ?shared_ptr是一种具有共享所有权的智能指针。shared_ptr管理的内存可以被多个对象持有,内部使用引用计数来管理内存;该指针支持复制和赋值和移动。由于shared_pt需要额外维护一个原子级别的引用计数,其内存占用和执行效率都远远不如裸指针。该指针适合用于共享权不明的场景,比如并发场景的多线程。shared_ptr可以被多个线程同时读,同时写时需要加锁。

   {
        shared_ptr<A> pA(new A());//pA指向A,拥有A的内存资源的所有权
        pA->print();// 访问A的方法
        cout<<"current Ref Count (" << pA.use_count() << ") "<<endl;
        //(一)支持拷贝
        shared_ptr<A> pA1(pA);//支持拷贝
        cout<<"current Ref Count (" << pA.use_count() << ") "<<endl;
        //(二)支持赋值
        shared_ptr<A> pA2(new A());//pA指向A,拥有A的内存资源的所有权
        pA2=pA; //支持赋值
        cout<<"current Ref Count (" << pA.use_count() << ") "<<endl;
        //(三)支持移动
        shared_ptr<A> pA3 = std::move(pA);//pA将所有权移交pA3,自己失去所有权
        if(pA == NULL) cout<<"pA = NULL\n";
        cout<<"current Ref Count (" << pA.use_count() << ") "<<endl;
    }

输出结果如下:

?可以看到,pA在移动前引用计数在一直增加,移动后,引用计数变为0,因为其引用计数被释放。

3、weak_ptr<T>

? ? ? ?weak_ptr是一种具有临时所有权的智能指针。当某个对象只有存在时才需要被访问,而且随时可能被他人删除时,可以使用它来跟踪该对象。需要获得临时所有权时,则将其转换为shared_ptr,此时如果原来的shared_ptr被销毁,则该对象的生命期将被延长至这个临时的shared_ptr同样被销毁为止。

????????shared_ptr的双向引用问题:

#include<memory>
class B;
class A{
    shared_ptr<B> b;
};
class B{
    shared_ptr<A> a;
};
auto pa = make_shared<A>();
auto pb = make_shared<B>();
pa->b = pb;
pb->a = pa;

????????pa和pb存在着循环引用,根据shared_ptr引用计数的原理,pa和pb都无法被正常的释放。 我们可以将其中的一个?shared_ptr修改为weak_ptr.

? ? ? ?weak_ptr可以认为是shared_ptr的补充,它可以解决shared_ptr双向引用的问题,经常需要和 shared_ptr 一起使用,例如用于类的继承场景:父类持有指向子类的shared_ptr,子类持有指向父类的weak_ptr。

二、智能指针的简易实现

1、unique_ptr的实现

unique_ptr成员变量只有一个裸指针mPointer

重写析构函数、拷贝构造函数、移动构造函数

然后需要重载运算符&、->、=

template<class T>
class unique_p{
public:
    // 构造函数
    unique_p(): mPointer(nullptr){}
    unique_p(T* p): mPointer(p){
        cout << "create smart pointer at " << static_cast<const void *>(p) <<endl;
    }
    // 拷贝构造函数
    unique_p(const unique_p &_p):mPointer(_p.mPointer){
        cout <<"Copy smart pointer at "<<static_cast<const void*>(_p.mPointer)<<endl;
    } 
    // 移动构造函数
    unique_p(unique_p&& _p)  {
        mPointer = _p.mPointer;
        _p.mPointer = NULL;
    }
    // 移动赋值函数
    unique_p& operator=(unique_p&& _p)  {
        mPointer = _p.mPointer;
        _p.mPointer = NULL;
        return *this;
    }
    // 重载*和->操作符  
    unique_p& operator*() const {return *this;}
    T* operator->() const {return this->mPointer;}
    // 重载=操作符                
    unique_p& operator =(const unique_p &_p) {
        if(this==&_p) return *this;
        this->reset();// 将原先的内存释放掉
        this->mPointer=_p.mPointer;
        return *this;
    } 
    // 析构函数
    ~unique_p(){
        this->reset(); // 实现内存资源自动销毁机制
    }
private:
    void reset() {
        if(mPointer) {
            cout << "real release smart pointer at " << static_cast<const void *>(mPointer) <<endl;
            delete mPointer;
        }
    }
private:
    T* mPointer;// C++实际指针
};

测试代码如下:

    // 创建共享指针1
    unique_p<A> pAOuter(new A());
    // 移动
    unique_p<A> sptr2(std::move(pAOuter));  // 调用移动构造函数
    cout << "------------------------------------------------" <<endl;
    {   
        unique_p<A> sptr3;
        sptr3 = std::move(sptr2);  // 调用移动赋值函数
    }

输出:

2、shared_ptr的实现

shared_ptr内部具有引用计数,所以其成员变量除了裸指针mPointer,还需要引用计数mRefCount。

重写析构函数、拷贝构造函数、移动构造函数

然后需要重载运算符&、->、=

template<class T>
class shared_p{
public:
    // 构造函数
    shared_p(): mPointer(nullptr),mRefCount(0){}
    shared_p(T* p): mPointer(p),mRefCount(0){
        cout << "create smart pointer at " << static_cast<const void *>(p) <<endl;
        if(mPointer) mRefCount++;// 引用计数+1
    }
    // 拷贝构造函数
    shared_p(const shared_p &_p):mPointer(_p.mPointer),mRefCount(_p.mRefCount){
        cout <<"Copy smart pointer at "<<static_cast<const void*>(_p.mPointer)<<endl;
        mRefCount++;// 引用计数+1
    } 
    // 移动构造函数
    shared_p(shared_p&& _p) {
        mPointer = _p.mPointer;
        mRefCount=_p.mRefCount;
        _p.mPointer = NULL;
        _p.mRefCount=0;
    }
    // 移动赋值函数
    shared_p& operator=(shared_p&& _p) {
        mPointer = _p.mPointer;
        mRefCount=_p.mRefCount;
        _p.mPointer = NULL;
        _p.mRefCount=0;
        return *this;
    }
    // 重载*和->操作符  
    shared_p& operator*() const {return *this;}
    T* operator->() const {return this->mPointer;}
    // 重载=操作符                
    shared_p& operator =(const shared_p &_p) {
        if(this==&_p) return *this;
        this->reset();// 将原先的内存释放掉
        this->mPointer=_p.mPointer;
        this->mRefCount=_p.mRefCount;
        mRefCount++;// 引用计数+1
        return *this;
    } 
    // 析构函数
    ~shared_p(){
        cout << "release smart pointer at " << static_cast<const void *>(mPointer) <<endl;
        this->reset(); // 实现内存资源自动销毁机制
    }
    int count() const {return mRefCount ? mRefCount:0;}
private:
    void reset() {
        if(mRefCount) {
            mRefCount--;
            if(mRefCount == 0) {
                cout << "real release smart pointer at " << static_cast<const void *>(mPointer) <<endl;
                delete mPointer;
            }
        }
    }
private:
    T* mPointer;// C++实际指针
    int mRefCount;// 引用计数
};

测试代码如下:

    // 创建共享指针1
    shared_p<A> pAOuter(new A());
    cout << "current Ref Count (" << pAOuter.count() << ") outer 1."<<endl;
    cout << "------------------------------------------------" <<endl;
    {   // 复制
        shared_p<A> pA(pAOuter);
        std::cout << "current Ref Count (" << pA.count() << ") inner."<<endl;
    }
    cout << "------------------------------------------------" <<endl;
    cout << "current Ref Count (" << pAOuter.count() << ") outer 2."<<endl;
    // 赋值
    shared_p<A> spOuter2(new A());
    pAOuter = spOuter2;// 1处new出来的SomeClass将会被自动释放 
    cout << "current Ref Count (" << pAOuter.count() << ") outer 2."<<endl;
    // 移动
    cout << "------------------------------------------------" <<endl;
    shared_p<A> sptr2(std::move(pAOuter));  // 调用移动构造函数
    shared_p<A> sptr3;
    sptr3 = std::move(sptr2);  // 调用移动赋值函数
    cout << "current Ref Count (" << pAOuter.count() << ") outer 2."<<endl;

输出:

?总结

? 智能指针本质是一个C++类模板,封装了C++的普通类指针,覆写了析构函数、拷贝构造函数、移动构造函数,重载了*、->、=操作符函数。

如果本文对您有帮助,欢迎点赞支持!

  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2022-04-30 08:30:43  更:2022-04-30 08:30:45 
 
开发: 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/21 0:18:11-

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