自学内容网 自学内容网

C++:智能指针

前言

什么是智能指针呢,智能指针是包含重载运算符的类,其行为像常规指针,但智能指针能够及时、妥善地销毁动态分配的数据,并实现了明确的对象生命周期,因此更有价值。也就是说,能够避免一定程度上的内存泄漏。

常规(原始)指针存在的问题:无法直观了解内存分配的方式,及其容易造成内存泄漏,如:

SomeClass *ptrData = anObject.GetData();
ptrData->DoSomething();

上述代码中,没有显而易见的方法知道ptrData指向的内内存,比如:是否从堆中分配,是否由调用者负责释放,对象的析构函数是否会自动销毁该对象,模糊的了解会导致内存泄漏的风险。

智能指针的好处:

smart_pointer<SomeClass> spData = anObject.GetData();
spData->Display();
(*spData).Display();

智能指针的行为类似常规指针(这里将其称为原始指针),但通过重载的运算符和析构函数确保动 态分配的数据能够及时地销毁,从而提供了更多有用的功能。

那么智能指针是如何实现的呢?这个问题可以换成智能指针如何做到像常规指针那样,其实本质上就是重载了解引用运算符(*)和成员选择运算符(->),并通过模板类来实现。下面是一个最简单智能指针类的实现。

template<typename T>
class smart_pointer
{
   private:
          T* rawPtr;
   public:
          smart_pointer(T* pData):rawPtr(pData) {}  //构造函数
          ~smart_pointer() {delete rawPtr;}  //析构函数
          smart_pointer(const smart_pointer & anotherSP);  //拷贝构造函数
          smart_pointer& operator = (const smart_pointer& anotherSP);  //拷贝复制函数
          T& operator* () const
          {
               return *(rawPtr);
          }
          T* operator-> () const
          {
               return rawPtr;
          }
          
}

主要讨论四种智能指针:auto_ptr,unique_ptr,shared_ptr , weak_ptr。

RAII

RAII(Resource Acquisiton Is Initialization)是一种利用对象生命周期来控制程序资源的简单技术。简单的来说,就是在对象构造时获取资源,接着控制对资源的访问,使之在对象的声明周期内始终保持有效,最后在对象析构的时候释放资源。好处:不需要显示的释放资源,采用这种方式,对象所需的资源在其生命周期内始终保持有效。

Auto_ptr有一个问题,多个智能指针指向一个资源,无法确定资源的所有权是谁所拥有,比如auto2 = auto1,这个时候auto1将所有权转移给auto2,这个时候在访问auto1会报错。为了解决所有权不清晰的问题,提出了unique_ptr。

unique_ptr,把拷贝构造函数、拷贝赋值函数直接给禁止了,即不允许有auto2=auto1这种情况发生,但允许移动构造函数。所以独占指针独占指针,就是禁止拷贝嘛。

接下来进行具体的介绍。

auto_ptr

template<typename T>
class AutoPtr
{
public:
AutoPtr() :m_data(nullptr) {}
AutoPtr(T* data) :m_data(data) {}
AutoPtr(AutoPtr & other):m_data(other.release()){}
AutoPtr& operator = (AutoPtr<T>& other)
{
if (this == &other)
{
return *this;
}
m_data = other.release();
return *this;
}
~AutoPtr()
{
if (m_data != nullptr)
{
delete m_data;
m_data = nullptr;
}
}
T* get()
{
return  m_data;
}
T* release()  //所有权的转移,自己不再占用该指针
{
auto data = m_data;
m_data = nullptr;
return data;
}
void reset(T * data = nullptr)
{
if (m_data != data)
{
delete m_data;
m_data = data;
}
}
T* operator ->()
{
return m_data;
}
T& operator *()
{
return *m_data;
}

private:
T* m_data;
};

unique_ptr

独占智能指针,相比较auto智能指针,是强制禁止了拷贝构造函数。

template<typename T>
class UniquePtr
{
public:
UniquePtr() :m_data(nullptr) {}
UniquePtr(T* data) :m_data(data) {}
UniquePtr(const UniquePtr<T> & other) = delete;
UniquePtr(UniquePtr<T> && other) :m_data(other.release()){}
UniquePtr& operator = (const UniquePtr<T>& other) = delete;   //拷贝赋值函数给禁止掉了
UniquePtr& operator =(UniquePtr<T>&& other)   //但是允许移动赋值函数
{
if (this == &other)
{
return *this;
}
reset(other.release());
return *this;
}
~UniquePtr()
{
if (m_data != nullptr)
{
delete m_data;
m_data = nullptr;
}
}
T* get()
{
return  m_data;
}
T* release()  //所有权的转移
{
auto data = m_data;
m_data = nullptr;
return data;
}
T& operator [](int i)const
{
return m_data[i];
}
void reset(T* data = nullptr)
{
if (m_data != data)
{
delete m_data;
m_data = data;
}
}
void swap(UniquePtr<T>& other)
{
auto data = other.m_data;
other.m_data = m_data;
m_data = data;
}
T* operator ->()
{
return m_data;
}
T& operator *()
{
return *m_data;
}
explicit operator bool() const noexcept
{
return m_data != nullptr;
}

private:
T* m_data;
};

shared_ptr共享的智能指针

shared_ptr的目的是对象进行共享,核心实现是通过引用计数。每一个shared_ptr的拷贝都指向相同的内存。在最后一个shared_ptr析构的时候,内存才会被释放。

那么什么时候选择unique_ptr和shared_ptr,如果希望只有一个只能指针管理资源或者管理数组就用unique_ptr,如果希望多个智能指针管理同一个资源就用shared_ptr。

template<typename T>
class SharedPtr
{
public:
SharedPtr():m_data(nullptr),m_count(nullptr){}
SharedPtr(T* data) :m_data(data)
{
if (data != nullptr)
{
m_count = new int(1);
}
}
SharedPtr(const SharedPtr<T>& other) :m_data(other.m_data), m_count(other.m_count)
{
if (m_data != nullptr)
{
(*m_count)++;
}
}
SharedPtr(SharedPtr<T>&& other)noexcept :m_data(other.m_data), m_count(other.m_count)
{
other.m_data = nullptr;
other.m_count = nullptr;
}
~SharedPtr()
{
if (m_data != nullptr)
{
(*m_count)--;
if (*m_count <= 0)
{
delete m_data;
m_data = nullptr;
delete m_count;
m_count = nullptr;
}
}
}
T* get() const
{
return m_data;
}
void reset(T* data = nullptr)
{
if (m_data == data)
{
return;
}
if (m_data == nullptr)
{
if (data != nullptr)
{
m_data = data;
m_count = new int(1);
}
return;
}
(*m_count)--;
if (*m_count <= 0)
{
delete m_data;
m_data = nullptr;
delete m_count;
m_count = nullptr;
}
m_data = data;
if (data != nullptr)
{
m_count = new int(1);
}
}
int use_count()const
{
if (m_data == nullptr)
{
return 0;
}
return *m_count;
}
bool unique() const
{
if (m_data == nullptr)
{
return false;
}
return *m_count == 1;
}
void swap(SharedPtr<T>& other)
{
auto data = other.m_data;
auto count = other.m_count;
other.m_data = m_data;
other.m_count = m_count;
m_data = data;
m_count = count;
}
T* operator ->() const
{
return m_data;
}
T& operator *()const
{
return *m_data;
}
explicit operator bool() const noexcept
{
return m_data != nullptr;
}
SharedPtr& operator =(const SharedPtr<T>& other)
{
if (this == &other)
{
return *this;
}
m_data = other.m_data;
m_count = other.m_count;
(*m_count)++;
return *this;
}
SharedPtr& operator =(SharedPtr<T>&& other)noexcept
{
if (this == &other)
{
return *this;
}
m_data = other.m_data;
m_count = other.m_count;
other.m_data = nullptr;
other.m_count = nullptr;
return *this;
}
private:
T* m_data;
int* m_count;
};

看上上述代码总结一下,什么情况下引用计数会加什么情况引用计数会减。

首先要明确,shared_ptr需要维护的信息有两部分,指向共享资源的指针(m_data)以及指向控制信息的指针(m_count)。当创建一个shared_ptr的时候 std::shared_ptr<T>stpr1(new T)会有下图的组成。

当复制一个shared_ptr的时候 std::shared_ptr<T> sptr2 = sptr1,会有以下操作:

这个时候引用计数就会加1,释放一个就会减1。其实本质就是几个拷贝构造或者拷贝赋值的shared_ptr都指向同一块内存,唯一的区别就是引用计数。

weak ptr

shared ptr存在一个循环引用的问题。

循环引用是什么?看以下代码,最终离开作用域的时候引用计数没有减至0,因此会导致循环引用。而解决方案就是将A和B其中一个用weak_ptr来创建指针。

struct A;
struct B;

struct A{
       std::shared_ptr<B> bptr;
       ~A() {cout << "A is deleted!" <<endl; }
}

struct B{
       std::shared_ptr<A> aptr;
       //解决循环引用的方案就是将上面一行替换为 std::weak_ptr<A> aptr
       ~B() {cout << "B is deleted!" <<endl; }
}

void TestPtr()
{
     {
            std::shared_ptr<A> ap(new A);  //A的引用计数为1
            std::shared_ptr<B> bp(new B);  //B的引用计数为1
            ap->bptr = bp; //A的bptr指向B,B的引用计数加1,B的引用计数为2
            bp->aptr = ap; //B的aptr指向A,A的引用计数加1,A的引用计数为2
            //离开作用域,ap和bp析构时不会导致A和B的引用计数归0
     }
}

原文地址:https://blog.csdn.net/HR_Reborn/article/details/131355491

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!