栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > C/C++/C#

C++程序设计之智能指针的理论与实现

C/C++/C# 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

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,在头文件提供了3个新的智能指针类型,这些智能指针都是在裸指针的基础上封装而来的,对比如下:

对象所有权

执行效率

应用频率

安全性

特点

裸指针

最高

最高

较低

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

unique_ptr

专属所有权

较高

较高

较高

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

shared_ptr

共享所有权

较低

较低

较高

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

weak_ptr

临时所有权

较低

最低

较高

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

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

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

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

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

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

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

class A{
public:
    A(){cout << "A default constructor !"< 

1、unique_ptr

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

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

 输出结果如下:

2、shared_ptr

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

   {
        shared_ptr pA(new A());//pA指向A,拥有A的内存资源的所有权
        pA->print();// 访问A的方法
        cout<<"current Ref Count (" << pA.use_count() << ") "< 

输出结果如下:

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

3、weak_ptr

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

        shared_ptr的双向引用问题:

#include
class B;
class A{
    shared_ptr b;
};
class B{
    shared_ptr a;
};
auto pa = make_shared();
auto pb = make_shared();
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 unique_p{
public:
    // 构造函数
    unique_p(): mPointer(nullptr){}
    unique_p(T* p): mPointer(p){
        cout << "create smart pointer at " << static_cast(p) <(_p.mPointer)<操作符  
    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(mPointer) < 

测试代码如下:

    // 创建共享指针1
    unique_p pAOuter(new A());
    // 移动
    unique_p sptr2(std::move(pAOuter));  // 调用移动构造函数
    cout << "------------------------------------------------" < 

输出:

2、shared_ptr的实现

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

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

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

template
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(p) <(_p.mPointer)<操作符  
    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(mPointer) <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(mPointer) < 

测试代码如下:

    // 创建共享指针1
    shared_p pAOuter(new A());
    cout << "current Ref Count (" << pAOuter.count() << ") outer 1."< 

输出:

 总结

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

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

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/847629.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号