为什么需要智能指针?

简介: 为什么需要智能指针?

为什么需要智能指针

  1. 解决忘记释放内存导致内存泄漏的问题。
  2. 解决异常安全问题。
#include<iostream>
using namespace std;
int div()
{
  int a, b;
  cin >> a >> b;
  if (b == 0)
    throw invalid_argument("除0错误");
  return a / b;
}
void Func()
{
  // 1、如果p1这里new 抛异常会如何?
  // 2、如果p2这里new 抛异常会如何?
  // 3、如果div调用这里又会抛异常会如何?
  int* p1 = new int;
  int* p2 = new int;
  cout << div() << endl;
  delete p1;
  delete p2;
}
int main()
{
  try
  {
    Func();
  }
  catch (exception& e)
  {
    cout << e.what() << endl;
  }
  return 0;
}

问:如果p1这里new 抛异常会如何?

答:p1、p2不会开空间,内存没有释放

问:如果p2这里new 抛异常会如何?

答:p2不会开空间,内存没有得到释放

问:如果div调用这里又会抛异常会如何?

答:内存没有被释放。

那么如何解决呢?

可以利用智能指针来解决这个问题。

智能指针的使用及其原理

RAII

RAII(Resource Acquisition Is Initialization)是一种利用对象生命周期来控制程序资源(如内 存、文件句柄、网络连接、互斥量等等)的简单技术。

在对象构造时获取资源,接着控制对资源的访问使之在对象的生命周期内始终保持有效,最后在 对象析构的时候释放资源。借此,我们实际上把管理一份资源的责任托管给了一个对象。这种做 法有两大好处:

  • 不需要显示地释放资源。
  • 采用这种方式,对象所需的资源在其生命期内始终保持有效。

我们用一个类来封装一下这个指针,实现如下:

#include<iostream>
using namespace std;
namespace hayaizo
{
  template<class T>
  class smart_ptr
  {
  public:
    smart_ptr(T* ptr = nullptr)
      :_ptr(ptr)
    {}
    ~smart_ptr()
    {
      if (_ptr)
      {
        cout << "delete: " << _ptr << endl;
        delete _ptr;
      }
    }
  private:
    T* _ptr;
  };
}
int div()
{
  int a, b;
  cin >> a >> b;
  if (b == 0)
    throw invalid_argument("除0错误");
  return a / b;
}
void Func()
{
  hayaizo::smart_ptr<int> sp1(new int);
  hayaizo::smart_ptr<int> sp2(new int);
  cout << div() << endl;
}
int main(void)
{
  try {
    Func();
  }
  catch (const exception& e)
  {
    cout << e.what() << endl;
  }
  return 0;
}

运行结果:

既然是指针,那么也需要支持解引用*->,重载这两个符号就好了。

namespace hayaizo
{
  template<class T>
  class smart_ptr
  {
  public:
    smart_ptr(T* ptr = nullptr)
      :_ptr(ptr)
    {}
    ~smart_ptr()
    {
      if (_ptr)
      {
        cout << "delete: " << _ptr << endl;
        delete _ptr;
      }
    }
    T& operator*()
    {
      return *_ptr;
    }
    T* operator->()
    {
      return _ptr;
    }
  private:
    T* _ptr;
  };
}

但这样地封装会有一个致命的缺点,一个地址只能被一个智能指针指向,不然会导致同一块内存释放两次的问题,我们看看官方库中的auto_ptr是怎么解决的。

可以看到,sp1的地址变成了sp2的地址了,然后sp1的地址变成了nullptr

手搓一个低配版的auto_ptr

namespace hayaizo
{
  template<class T>
  class auto_ptr
  {
  public:
    auto_ptr(T* ptr = nullptr)
      :_ptr(ptr)
    {}
    auto_ptr(auto_ptr<T>&ap)
      :_ptr(ap._ptr)
    {
      ap._ptr = nullptr;
    }
    auto_ptr<T> operator=(auto_ptr<T>& ap)
    {
      if (this != &ap)
      {
        if (_ptr)
        {
          delete _ptr;
        }
        _ptr = ap._ptr;
        ap._ptr = nullptr;
      }
      return *this;
    }
    ~auto_ptr()
    {
      if (_ptr)
      {
        cout << "delete: " << _ptr << endl;
        delete _ptr;
      }
    }
    T& operator*()
    {
      return *_ptr;
    }
    T* operator->()
    {
      return _ptr;
    }
  private:
    T* _ptr;
  };
}

但这样确实可以解决问题,但是已经失去了原生指针的功能了,原生指针是支持同一个地址被很多个指针指向的,在介绍解决方法之前得先介绍unique_ptr

unique_ptr

unique_ptr非常粗暴,直接不让你拷贝,把拷贝构造禁掉了。

template<class T>
  class unique_ptr
  {
  public:
    unique_ptr(T* ptr = nullptr)
      :_ptr(ptr)
    {}
    unique_ptr(unique_ptr<T>& ap)=delete
    {}
    unique_ptr<T> operator=(unique_ptr<T>& ap)=delete
    {}
    ~unique_ptr()
    {
      if (_ptr)
      {
        cout << "delete: " << _ptr << endl;
        delete _ptr;
      }
    }
    T& operator*()
    {
      return *_ptr;
    }
    T* operator->()
    {
      return _ptr;
    }
  private:
    T* _ptr;
  };

shared_ptr

shared_ptr的原理:是通过引用计数的方式来实现多个shared_ptr对象之间共享资源。

  1. shared_ptr在其内部,给每个资源都维护了着一份计数,用来记录该份资源被几个对象共 享。
  2. 在对象被销毁时(也就是析构函数调用),就说明自己不使用该资源了,对象的引用计数减 一。
  3. 如果引用计数是0,就说明自己是最后一个使用该资源的对象,必须释放该资源。
  4. 如果不是0,就说明除了自己还有其他对象在使用该份资源,不能释放该资源,否则其他对 象就成野指针了。

比如我用三个智能指针指向地址0x00112233,因此,计数为3,当计数等于0的时候再进行销毁。

那么,这里的计数可以单纯用一个int _cnt或者static int _cnt来表示吗?

答案是否定的。

如果是int _cnt,那么每个对象都是单独的计数。

如果是``static int _cnt`,那么每个对象都是用的同一份计数。

所以这里需要用一个指针来表示计数。

template<class T>
  class shared_ptr
  {
  public:
    shared_ptr(T* ptr = nullptr)
      :_ptr(ptr)
      ,_cnt(new int(1))
    {}
    shared_ptr(shared_ptr<T>& ap)
      :_ptr(ap._ptr)
      ,_cnt(ap._cnt)
    {}
    void Release()
    {
      if (--(*_cnt) == 0)
      {
        cout << "delete: " << _ptr << endl;
        delete _ptr;
      }
    }
    shared_ptr<T>& operator=(const shared_ptr<T>& sp)
    {
      //p1=p1的情况
      if (_ptr == sp._ptr)
      {
        return *this;
      }
      Release();
      _ptr = sp._ptr;
      _cnt = sp._cnt;
      (*_cnt)++;
      return *this;
    }
    int use_count()
    {
      return *_cnt;
    }
    T* get() const
    {
      return _ptr;
    }
    T& operator*()
    {
      return *_ptr;
    }
    T* operator->()
    {
      return _ptr;
    }
    ~shared_ptr()
    {
      Release();
    }
  private:
    T* _ptr;
    int* _cnt;
  };
}

循环引用

这里的n1n2的引用计数都是2,所以形成了相互制约的局面。

n1的销毁看n2n2的销毁看n1

weak_ptr

weak_ptr是为配合shared_ptr而引入的一种智能指针。

weak_ptr可以从一个shared_ptr或另一个weak_ptr对象构造,它的构造和析构不会引起shared_ptr引用记数的增加或减少

template<class T>
  class weak_ptr
  {
  public:
    weak_ptr()
      :_ptr(nullptr)
    {}
    weak_ptr(const shared_ptr<T>& sp)
      :_ptr(sp.get())
    {}
    weak_ptr<T>& operator=(const shared_ptr<T>& sp)
    {
      _ptr = sp.get();
      return *this;
    }
    T& operator*()
    {
      return *_ptr;
    }
    T* operator->()
    {
      return _ptr;
    }
  private:
    T* _ptr;
  };
struct Node
{
  int _val;
  hayaizo::weak_ptr<Node> _next;
  hayaizo::weak_ptr<Node> _prev;
  ~Node()
  {
    cout << "~Node" << endl;
  }
};
int main(void)
{
  hayaizo::shared_ptr<Node> n1(new Node);
  hayaizo::shared_ptr<Node> n2(new Node);
  n1->_next = n2;
  n2->_prev = n1;
  return 0;
}

很简单,就是n1内部的指针不参与引用计数,用另外的类封装起来就好了,就不会动shared_ptr<T>里面的引用计数了。

定制删除器

其实就是个仿函数,可以自己传删除方案。

//默认删除器
  template<class T>
  struct Delete
  {
    void operator()(T* ptr)
    {
      cout << "delete: " << ptr << endl;
      delete ptr;
    }
  };
  template<class T,class D=Delete<T>>
  class shared_ptr
  {
  public:
    shared_ptr(T* ptr = nullptr)
      :_ptr(ptr)
      ,_cnt(new int(1))
    {}
    shared_ptr(shared_ptr<T>& ap)
      :_ptr(ap._ptr)
      ,_cnt(ap._cnt)
    {}
    void Release()
    {
      if (--(*_cnt) == 0)
      {
        cout << "delete: " << _ptr << endl;
        D del;
        del(_ptr);
        //D()(_ptr);匿名对象调用()
      }
    }
    shared_ptr<T>& operator=(const shared_ptr<T>& sp)
    {
      //p1=p1的情况
      if (_ptr == sp._ptr)
      {
        return *this;
      }
      Release();
      _ptr = sp._ptr;
      _cnt = sp._cnt;
      (*_cnt)++;
      return *this;
    }
    int use_count()
    {
      return *_cnt;
    }
    T* get() const
    {
      return _ptr;
    }
    T& operator*()
    {
      return *_ptr;
    }
    T* operator->()
    {
      return _ptr;
    }
    ~shared_ptr()
    {
      Release();
    }
  private:
    T* _ptr;
    int* _cnt;
  };
template<class T>
struct DeleteArray
{
  void operator()(T* ptr)
  {
    cout << "delete[]" << ptr << endl;
    delete[] ptr;
  }
};
template<class T>
struct Free
{
  void operator()(T* ptr)
  {
    cout << "free" << ptr << endl;
    free(ptr);
  }
}; 

总代码:

//#include<iostream>
//using namespace std;
//
//int div()
//{
//  int a, b;
//  cin >> a >> b;
//  if (b == 0)
//    throw invalid_argument("除0错误");
//  return a / b;
//}
//void Func()
//{
//  // 1、如果p1这里new 抛异常会如何?
//  // 2、如果p2这里new 抛异常会如何?
//  // 3、如果div调用这里又会抛异常会如何?
//  int* p1 = new int;
//  int* p2 = new int;
//  cout << div() << endl;
//  delete p1;
//  delete p2;
//}
//int main()
//{
//  try
//  {
//    Func();
//  }
//  catch (exception& e)
//  {
//    cout << e.what() << endl;
//  }
//  return 0;
//}
#include<iostream>
using namespace std;
namespace hayaizo
{
  template<class T>
  class auto_ptr
  {
  public:
    auto_ptr(T* ptr = nullptr)
      :_ptr(ptr)
    {}
    auto_ptr(auto_ptr<T>&ap)
      :_ptr(ap._ptr)
    {
      ap._ptr = nullptr;
    }
    auto_ptr<T> operator=(auto_ptr<T>& ap)
    {
      if (this != &ap)
      {
        if (_ptr)
        {
          delete _ptr;
        }
        _ptr = ap._ptr;
        ap._ptr = nullptr;
      }
      return *this;
    }
    ~auto_ptr()
    {
      if (_ptr)
      {
        cout << "delete: " << _ptr << endl;
        delete _ptr;
      }
    }
    T& operator*()
    {
      return *_ptr;
    }
    T* operator->()
    {
      return _ptr;
    }
  private:
    T* _ptr;
  };
  template<class T>
  class unique_ptr
  {
  public:
    unique_ptr(T* ptr = nullptr)
      :_ptr(ptr)
    {}
    unique_ptr(unique_ptr<T>& ap)=delete
    {}
    unique_ptr<T> operator=(unique_ptr<T>& ap)=delete
    {}
    ~unique_ptr()
    {
      if (_ptr)
      {
        cout << "delete: " << _ptr << endl;
        delete _ptr;
      }
    }
    T& operator*()
    {
      return *_ptr;
    }
    T* operator->()
    {
      return _ptr;
    }
  private:
    T* _ptr;
  };
  //默认删除器
  template<class T>
  struct Delete
  {
    void operator()(T* ptr)
    {
      cout << "delete: " << ptr << endl;
      delete ptr;
    }
  };
  template<class T,class D=Delete<T>>
  class shared_ptr
  {
  public:
    shared_ptr(T* ptr = nullptr)
      :_ptr(ptr)
      ,_cnt(new int(1))
    {}
    shared_ptr(shared_ptr<T>& ap)
      :_ptr(ap._ptr)
      ,_cnt(ap._cnt)
    {}
    void Release()
    {
      if (--(*_cnt) == 0)
      {
        cout << "delete: " << _ptr << endl;
        D del;
        del(_ptr);
        //D()(_ptr);匿名对象调用()
      }
    }
    shared_ptr<T>& operator=(const shared_ptr<T>& sp)
    {
      //p1=p1的情况
      if (_ptr == sp._ptr)
      {
        return *this;
      }
      Release();
      _ptr = sp._ptr;
      _cnt = sp._cnt;
      (*_cnt)++;
      return *this;
    }
    int use_count()
    {
      return *_cnt;
    }
    T* get() const
    {
      return _ptr;
    }
    T& operator*()
    {
      return *_ptr;
    }
    T* operator->()
    {
      return _ptr;
    }
    ~shared_ptr()
    {
      Release();
    }
  private:
    T* _ptr;
    int* _cnt;
  };
  template<class T>
  class weak_ptr
  {
  public:
    weak_ptr()
      :_ptr(nullptr)
    {}
    weak_ptr(const shared_ptr<T>& sp)
      :_ptr(sp.get())
    {}
    weak_ptr<T>& operator=(const shared_ptr<T>& sp)
    {
      _ptr = sp.get();
      return *this;
    }
    T& operator*()
    {
      return *_ptr;
    }
    T* operator->()
    {
      return _ptr;
    }
  private:
    T* _ptr;
  };
}
struct Node
{
  int _val;
  hayaizo::weak_ptr<Node> _next;
  hayaizo::weak_ptr<Node> _prev;
  ~Node()
  {
    cout << "~Node" << endl;
  }
};
template<class T>
struct DeleteArray
{
  void operator()(T* ptr)
  {
    cout << "delete[]" << ptr << endl;
    delete[] ptr;
  }
};
template<class T>
struct Free
{
  void operator()(T* ptr)
  {
    cout << "free" << ptr << endl;
    free(ptr);
  }
}; 
int main(void)
{
  /*hayaizo::shared_ptr<Node> n1(new Node);
  hayaizo::shared_ptr<Node> n2(new Node);
  n1->_next = n2;
  n2->_prev = n1;*/
  hayaizo::shared_ptr<Node, DeleteArray<Node>> n1(new Node[5]);
  hayaizo::shared_ptr<Node> n2(new Node);
  hayaizo::shared_ptr<int, DeleteArray<int>> n3(new int[5]);
  hayaizo::shared_ptr<int, Free<int>> n4((int*)malloc(sizeof(int)));
  return 0;
}
目录
相关文章
|
6月前
|
安全 程序员 编译器
C++中的RAII(资源获取即初始化)与智能指针
C++中的RAII(资源获取即初始化)与智能指针
82 0
|
6月前
|
安全 程序员 C++
C++中的智能指针:从原始指针到现代内存管理
C++中的智能指针:从原始指针到现代内存管理
49 0
|
6月前
|
C++
C++:一文读懂智能指针
C++:一文读懂智能指针
99 0
|
6月前
|
消息中间件 Kubernetes NoSQL
智能指针的种类以及使用场景
智能指针的种类以及使用场景
|
6月前
|
安全 C++
c++11新特性——智能指针详解
c++11新特性——智能指针详解
|
6月前
|
安全 C++ 容器
C++中的智能指针:自动内存管理的利器
C++中的智能指针:自动内存管理的利器
83 0
|
6月前
|
设计模式 Rust Java
【一起学Rust | 设计模式】习惯语法——默认特质、集合智能指针、析构函数
【一起学Rust | 设计模式】习惯语法——默认特质、集合智能指针、析构函数
96 0
|
6月前
|
存储 Rust C++
C++智能指针
【2月更文挑战第14天】介绍C++智能指针
49 0
|
6月前
|
算法 安全 C++
C++ Effective Modern Pointer (智能指针模块)
C++ Effective Modern Pointer (智能指针模块)