深入理解掌握智能指针

智能指针的介绍

裸指针到底有什么不好,写过一些C++代码的人应该都能知道,比如下面的原因:

  1. 忘记释放资源,导致资源泄露(常发生内存泄漏问题)
  2. 同一资源释放多次,导致释放野指针,程序崩溃
  3. 代码的后面写了释放资源的代码,但是由于程序逻辑满足条件,从中间return掉了,导致释放资源的代码未被执行到
  4. 代码运行过程中发生异常,随着异常栈展开,导致释放资源的代码未被执行到

智能指针,主要体现在用户可以不关注资源的释放,因为智能指针会帮你完全管理资源的释放,它会保证无论程序逻辑怎么跑,正常执行或者产生异常,资源在出作用域(过期)的情况下,一定会进行释放。

1.使用裸指针

int main()
{
    int *p = new int(10);
    
    *p = 3;

    //如果*p > 1,就会直接结束这个函数,后边的delete 并没有被执行
    if(*p > 1) return 1;
    
    delete p; //如果忘记写就会导致内存泄漏
    return 0;
}

2.封装类似的智能指针

template<typename T>
class myauto_ptr
{
    public:
        myauto_ptr(T *ptr = nullptr): mptr(ptr)
        {}
        ~myauto_ptr()
        {
            cout << "释放" << endl;
            delete mptr;
        }

        T& operator*() 
        {
            return *mptr;
        }

        T* operator->() 
        {
            return mptr;
        }
    private:
        T *mptr;
};
int main()
{
    //这个对象是在栈上开辟的,出了这个作用域,对象就会执行到析构对象,析构对象就释放掉了内存资源mptr
    myauto_ptr<int> p(new int(10));
    return 0;
}

  3.不带引用计数的智能指针:#include <memory>

auto_ptr : 不推荐使用

C++11标准:

        scoped_ptr:不支持拷贝构造,和赋值重载,实现到私有权限,无法访问,后期g++ 10没有该对象了

        unique_ptr:推荐使用

不加深拷贝代码: 

myauto_ptr(const myauto_ptr &ptr) //深拷贝
{
    mptr = new T(*ptr.mptr);
}

main运行这个代码: 

myauto_ptr<int> p1(new int);
//如果我们希望它们指向的同一块资源
myauto_ptr<int> p2(p1);

这个main函数运行,代码直接崩溃,问题出在默认的拷贝构造函数做的是浅拷贝,两个智能指针都持有一个new int资源,p2先析构释放了资源,到p1析构的时候,就成了delete野指针了,造成程序崩溃。所以这里引出来智能指针需要解决的两件事情: 

  1. 怎么解决智能指针的浅拷贝问题
  2. 多个智能指针指向同一个资源的时候,怎么保证资源只释放一次,而不是每个智能指针都释放一次,造成代码运行不可预期的严重后果

auto_ptr源码

template<typename _Tp>
    class auto_ptr
    {
    private:
      _Tp* _M_ptr;
      
    public:
      /// The pointed-to type.
      typedef _Tp element_type;
      
      /**
       *  @brief  An %auto_ptr is usually constructed from a raw pointer.
       *  @param  __p  A pointer (defaults to NULL).
       *
       *  This object now @e owns the object pointed to by @a __p.
       */
      explicit
      auto_ptr(element_type* __p = 0) throw() : _M_ptr(__p) { }

      /**
       *  @brief  An %auto_ptr can be constructed from another %auto_ptr.
       *  @param  __a  Another %auto_ptr of the same type.
       *
       *  This object now @e owns the object previously owned by @a __a,
       *  which has given up ownership.
       */
      auto_ptr(auto_ptr& __a) throw() : _M_ptr(__a.release()) { }

      /**
       *  @brief  An %auto_ptr can be constructed from another %auto_ptr.
       *  @param  __a  Another %auto_ptr of a different but related type.
       *
       *  A pointer-to-Tp1 must be convertible to a
       *  pointer-to-Tp/element_type.
       *
       *  This object now @e owns the object previously owned by @a __a,
       *  which has given up ownership.
       */
      template<typename _Tp1>
        auto_ptr(auto_ptr<_Tp1>& __a) throw() : _M_ptr(__a.release()) { }

      /**
       *  @brief  %auto_ptr assignment operator.
       *  @param  __a  Another %auto_ptr of the same type.
       *
       *  This object now @e owns the object previously owned by @a __a,
       *  which has given up ownership.  The object that this one @e
       *  used to own and track has been deleted.
       */
      auto_ptr&
      operator=(auto_ptr& __a) throw()
      {
	reset(__a.release());
	return *this;
      }

      /**
       *  @brief  %auto_ptr assignment operator.
       *  @param  __a  Another %auto_ptr of a different but related type.
       *
       *  A pointer-to-Tp1 must be convertible to a pointer-to-Tp/element_type.
       *
       *  This object now @e owns the object previously owned by @a __a,
       *  which has given up ownership.  The object that this one @e
       *  used to own and track has been deleted.
       */
      template<typename _Tp1>
        auto_ptr&
        operator=(auto_ptr<_Tp1>& __a) throw()
        {
	  reset(__a.release());
	  return *this;
	}

      /**
       *  When the %auto_ptr goes out of scope, the object it owns is
       *  deleted.  If it no longer owns anything (i.e., @c get() is
       *  @c NULL), then this has no effect.
       *
       *  The C++ standard says there is supposed to be an empty throw
       *  specification here, but omitting it is standard conforming.  Its
       *  presence can be detected only if _Tp::~_Tp() throws, but this is
       *  prohibited.  [17.4.3.6]/2
       */
      ~auto_ptr() { delete _M_ptr; }
      
      /**
       *  @brief  Smart pointer dereferencing.
       *
       *  If this %auto_ptr no longer owns anything, then this
       *  operation will crash.  (For a smart pointer, <em>no longer owns
       *  anything</em> is the same as being a null pointer, and you know
       *  what happens when you dereference one of those...)
       */
      element_type&
      operator*() const throw() 
      {
	__glibcxx_assert(_M_ptr != 0);
	return *_M_ptr; 
      }
      
      /**
       *  @brief  Smart pointer dereferencing.
       *
       *  This returns the pointer itself, which the language then will
       *  automatically cause to be dereferenced.
       */
      element_type*
      operator->() const throw() 
      {
	__glibcxx_assert(_M_ptr != 0);
	return _M_ptr; 
      }
      
      /**
       *  @brief  Bypassing the smart pointer.
       *  @return  The raw pointer being managed.
       *
       *  You can get a copy of the pointer that this object owns, for
       *  situations such as passing to a function which only accepts
       *  a raw pointer.
       *
       *  @note  This %auto_ptr still owns the memory.
       */
      element_type*
      get() const throw() { return _M_ptr; }
      
      /**
       *  @brief  Bypassing the smart pointer.
       *  @return  The raw pointer being managed.
       *
       *  You can get a copy of the pointer that this object owns, for
       *  situations such as passing to a function which only accepts
       *  a raw pointer.
       *
       *  @note  This %auto_ptr no longer owns the memory.  When this object
       *  goes out of scope, nothing will happen.
       */
      element_type*
      release() throw()
      {
	element_type* __tmp = _M_ptr;
	_M_ptr = 0;
	return __tmp;
      }
      
      /**
       *  @brief  Forcibly deletes the managed object.
       *  @param  __p  A pointer (defaults to NULL).
       *
       *  This object now @e owns the object pointed to by @a __p.  The
       *  previous object has been deleted.
       */
      void
      reset(element_type* __p = 0) throw()
      {
	if (__p != _M_ptr)
	  {
	    delete _M_ptr;
	    _M_ptr = __p;
	  }
      }
      
      /** 
       *  @brief  Automatic conversions
       *
       *  These operations are supposed to convert an %auto_ptr into and from
       *  an auto_ptr_ref automatically as needed.  This would allow
       *  constructs such as
       *  @code
       *    auto_ptr<Derived>  func_returning_auto_ptr(.....);
       *    ...
       *    auto_ptr<Base> ptr = func_returning_auto_ptr(.....);
       *  @endcode
       *
       *  But it doesn't work, and won't be fixed. For further details see
       *  http://cplusplus.github.io/LWG/lwg-closed.html#463
       */
      auto_ptr(auto_ptr_ref<element_type> __ref) throw()
      : _M_ptr(__ref._M_ptr) { }
      
      auto_ptr&
      operator=(auto_ptr_ref<element_type> __ref) throw()
      {
	if (__ref._M_ptr != this->get())
	  {
	    delete _M_ptr;
	    _M_ptr = __ref._M_ptr;
	  }
	return *this;
      }
      
      template<typename _Tp1>
        operator auto_ptr_ref<_Tp1>() throw()
        { return auto_ptr_ref<_Tp1>(this->release()); }

      template<typename _Tp1>
        operator auto_ptr<_Tp1>() throw()
        { return auto_ptr<_Tp1>(this->release()); }
    }

 总结:auto_ptr 永远是最后一个对象持有资源(可以用于转移对象持有权),就像以上图片如果再访问 *ap1 的话,就会引发报错。因为ap1是野指针,所以不推荐使用auto_ptr 

unique_ptr

unique_ptr(const unique_ptr<T>&)  = delete;

unique_ptr<T>& operator=(const unique_ptr<T>&) = delete;

//防止智能指针发生浅拷贝发生问题,把左值引用拷贝和左值赋值重载函数delete掉了,不能用
unique_ptr(unique_ptr&&) = default;

unique_ptr& operator=(unique_ptr&&) = default;

//但是支持右值拷贝构造,也就是说,unique_ptr智能指针可以通过右值引用进行拷贝构造和赋值操作,
//或者在产生unique_ptr临时对象的地方,如把unique_ptr作为函数的返回值时

示例代码:

template<typename T>
unique_ptr<T> getSmartPtr()
{
    unique<T> ptr(new int);
    return ptr;
}

int main()
{
    unique_ptr<int> p = getSmartPtr<int>();
    p = getSmartPtr<int>();

    unique_ptr<int> p1(new int);
    unique_ptr<int> p2(std::move(p1));

    return 0;
}

4.带引用计数的智能指针:#include <memory>

1.带引用计数:多个智能指针可以管理同一个资源。给每个对象资源匹配一个引用计数

        -》给一个资源做赋值或者拷贝构造的时候,引用计数加1

        -》一个资源出了它的作用域之后,引用计数减1,如果引用计数count == 0,资源就释放了

2.shared_ptr :强智能指针,可以改变资源的引用计数

3.weak_ptr :弱智能指针,不会改变资源的引用计数

模拟shared_ptr源代码示例:

//对资源进行引用计数的类
template<typename T>
class RefCnt
{
    public:
        RefCnt(T *ptr = nullptr) : mptr(ptr)
        {
            if(mptr != nullptr)
            {
                mcount = 1;
            }
        }
        void addRef() { mcount ++;} //增加资源的引用计数
        int delRef() { return --mcount;}
    private:
        T *mptr;
        int mcount; //库里边的mcount是atomic_int CAS锁保证线程安全的
};

template<typename T>
class myauto_ptr
{
    public:
        myauto_ptr(T *ptr = nullptr): mptr(ptr)
        {
            mpRefCnt = new RefCnt<T>(mptr);
        }
        ~myauto_ptr()
        {
            if(0 == mpRefCnt->delRef()) 
            {
                delete mptr;
                mptr = nullptr;
            }
        }
        myauto_ptr(const myauto_ptr<T> &src):mptr(src.mptr),mpRefCnt(src.mpRefCnt)
        {
            if(mptr != nullptr) mpRefCnt->addRef();

        }
        myauto_ptr& operator= (const myauto_ptr<T> &src)
        {
            if(this == &src) return *this;
            if (0 == mpRefCnt->delRef()) delete mptr;
            mptr = src.mptr;
            mpRefCnt = src.mpRefCnt;
            mpRefCnt->addRef();

        }
        T& operator*() 
        {
            return *mptr;
        }

        T* operator->() 
        {
            return mptr;
        }
    private:
        T *mptr; //指向资源的指针
        RefCnt<T> *mpRefCnt; //指向该资源引用计数对象的指针
};
int main()
{   
    myauto_ptr<int> ap1(new int);
    myauto_ptr<int> ap2(ap1);

    return 0;
}

5.shared_ptr的交叉引用问题

#include <iostream>
#include <memory>
using namespace std; 

class B;
class A
{
    public:
        A() {cout << "A()" << endl;}
        ~A(){cout << "~A()" << endl;}
        shared_ptr<B> _ptrb;
};

class B
{
    public:
        B() {cout << "B()" << endl;}
        ~B(){cout << "~B()" << endl;}
        shared_ptr<A> _ptrb;
};

int main()
{   
    shared_ptr<A> pa(new A());
    shared_ptr<B> pb(new B());

    cout << pa.use_count() << endl; 
    cout << pb.use_count() << endl; 

    return 0;
}

 如果加上两行代码:

 定义对象的时候,用强智能指针;引用对象的地方使用弱指针,注意:弱智能指针是无法调用对象的成员的,它只是一个观察的作用,可以在使用的时候升级为强指针shared_ptr<A> ps = _ptra.lock();

#include <iostream>
#include <memory>
using namespace std; 

class B;
class A
{
    public:
        A() {cout << "A()" << endl;}
        ~A(){cout << "~A()" << endl;}
        void testA()
        {
            cout << "调用" <<endl;
        }
        weak_ptr<B> _ptrb;
};

class B
{
    public:
        B() {cout << "B()" << endl;}
        ~B(){cout << "~B()" << endl;}
        void func(){
            //_ptra->testA(); 不能直接就调用
            shared_ptr<A> ps = _ptra.lock();//提升方法
            if(ps!= nullptr)
            {
                ps->testA();
            }
            cout << ps.use_count() << endl; 
            
        } //出了func的作用域就被调用了析构函数,然后引用计数减1
        weak_ptr<A> _ptra;
};

int main()
{   
    shared_ptr<A> pa(new A());
    shared_ptr<B> pb(new B());

    pa->_ptrb = pb;
    pb->_ptra = pa;

    pb->func();
    cout << pa.use_count() << endl; 
    cout << pb.use_count() << endl; 
    
    return 0;
}

多线程访问共享对象的线程安全问题

#include <iostream>
#include <thread>
#include <memory>
using namespace std; 


class A
{
    public:
        A() {cout << "A()" << endl;}
        ~A(){cout << "~A()" << endl;}
        void testA()
        {
            cout << "调用" <<endl;
        }

};

void handler01(A *p)
{
    std::this_thread::sleep_for(std::chrono::seconds(10));
    p->testA();
}

int main()
{   
    A *p = new A();
    thread t1(handler01, p);
    delete p;
    t1.join();
    return 0;
}

以上代码A对象析构了,在handler01里边还能执行testA函数,明显很危险,改为弱智能指针加强智能指针

#include <iostream>
#include <thread>
#include <memory>
using namespace std; 

class A
{
    public:
        A() {cout << "A()" << endl;}
        ~A(){cout << "~A()" << endl;}
        void testA()
        {
            cout << "调用" <<endl;
        }

};

void handler01(weak_ptr<A> pw)
{
    std::this_thread::sleep_for(std::chrono::seconds(2));
    shared_ptr<A> ps = pw.lock();

    //ps 访问对象的时候,需要侦测一下A对象是否存活
    if(ps != nullptr) ps->testA();
    else cout << "对象已经析构,不能再访问!" << endl;
    
}

int main()
{   
    {
        shared_ptr<A> p(new A());
        thread t1(handler01, weak_ptr<A>(p) );
        t1.detach();
    }
    std::this_thread::sleep_for(std::chrono::seconds(10));
    return 0;
}

智能指针的删除器 deletor

智能指针:能够保证资源绝对的释放  delete ptr

默认的删除器:

template<typename T>
class Deletor
{
    public:
        void operator()(T *ptr)
        {
            delete ptr;
        }
}

管理数组的自定义删除器:

#include <iostream>
#include <thread>
#include <memory>
using namespace std; 

template<typename T>
class MyDeletor
{
    public:
        void operator()(T *ptr) const
        {
            cout << "call operator" <<endl;
            delete []ptr; //释放数组资源指针
        }
};

int main()
{   
    //管理的数组资源,不可能用默认的删除器,因为是 delete ptr;
    unique_ptr<int,MyDeletor<int>> ptr(new int[100]); 
    return 0;
}

文件资源管理:

#include <iostream>
#include <thread>
#include <memory>
using namespace std; 

template<typename T>
class MyFileDeletor
{
    public:
        void operator()(T *ptr) const
        {
            cout << "call operator" <<endl;
            fclose(ptr); //关闭文件
        }
};

int main()
{   
    //管理的文件打开资源,不可能用默认的删除器,因为是fcolse;
    unique_ptr<FILE,MyFileDeletor<FILE>> ptr(fopen("data.txt","w")); 
    return 0;
}

lambda表达式(本身就是函数对象)代替函数对象(小括号运算符重载的就叫函数对象)来处理:

int main()
{   
    //使用lambda表达式来处理删除器
    unique_ptr<int,function<void(int *)>> ptr(new int[100],
        [](int *p)->void {cout << "call" << endl;delete []p;}
    ); 
    return 0;
}
int main()
{   
    //使用lambda表达式来处理删除器
    unique_ptr<FILE,function<void(FILE *)>> ptr(fopen("data.txt","w"),
        [](FILE *p)->void {cout << "call" << endl;fclose(p);}
    ); 
    return 0;
}

 

 参考:深入掌握C++智能指针_大秦坑王的专栏-CSDN博客知识的学习在于点滴记录,坚持不懈;知识的学习要有深度和广度,不能只流于表面,坐井观天;知识要善于总结,不仅能够理解,更知道如何表达!目录智能指针介绍自己实现智能指针不带引用计数的智能指针带引用计数的智能指针多线程访问共享对象问题自定义删除器智能指针介绍学习C++的人,一直在接触裸指针,一边感受着它的强大,一边感受着它的坑爹。当然,坑不坑爹在于开发者,指针本身近乎完美,但奈何用的人比较猥琐...https://blog.csdn.net/QIANGWEIYUAN/article/details/88562935?spm=1001.2014.3001.5502C++智能指针的enable_shared_from_this和shared_from_this机制_大秦坑王的专栏-CSDN博客知识的学习在于点滴记录,坚持不懈;知识的学习要有深度和广度,不能只流于表面,坐井观天;知识要善于总结,不仅能够理解,更知道如何表达!目录问题代码代码清单1代码清单2shared_ptr原理分析如果不熟悉C++带引用计数的智能指针shared_ptr和weak_ptr,请参考我的另一篇介绍智能指针的博客:https://blog.csdn.net/QIANGWEIYUAN/article/d...https://blog.csdn.net/QIANGWEIYUAN/article/details/88973735?spm=1001.2014.3001.5502

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
智能指针C++中的一种特殊类型的指针,它能够自动管理内存的释放,避免了手动释放内存的繁琐和容易出错的问题。C++11引入了几种智能指针,包括带引用计数的智能指针和不带引用计数的智能指针,如auto_ptr, scoped_ptr, unique_ptr, shared_ptr, weak_ptr等。\[1\] 带引用计数的智能指针(shared_ptr)使用引用计数来跟踪指针的引用次数,当引用计数为0时,自动释放内存。这种智能指针适用于多个指针共享同一块内存的情况,可以避免内存泄漏和悬挂指针的问题。 不带引用计数的智能指针(unique_ptr)则采用了独占所有权的方式,确保每个指针只能指向一个对象。当指针超出作用域或被显式释放时,内存会被自动释放。这种智能指针适用于需要独占资源的情况,如动态分配的内存、文件句柄等。 自己实现智能指针可以通过重载指针操作符和使用引用计数等技术来实现。但需要注意的是,自己实现智能指针需要考虑线程安全性和异常安全性等问题,确保指针的正确释放和资源的正确管理。 然而,需要注意的是,智能指针并不能解决所有的问题。例如,当存在循环引用时,带引用计数的智能指针(shared_ptr)可能导致内存泄漏。此外,使用智能指针时也需要注意避免裸指针和智能指针混用的情况,以免出现悬挂指针或重复释放内存的问题。\[2\]\[3\] 总之,深入掌握C++智能指针需要了解各种智能指针的原理、用法和适用场景,并注意使用智能指针的注意事项,以确保代码的正确性和安全性。 #### 引用[.reference_title] - *1* *2* [【C++】深入掌握智能指针](https://blog.csdn.net/weixin_45853856/article/details/121184992)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] - *3* [722-深入掌握C++智能指针](https://blog.csdn.net/LINZEYU666/article/details/120982178)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

CID( ͡ _ ͡°)

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值