RALL机制和C++11智能指针详解

一、RALL机制
RAII 是 resource acquisition is initialization 的缩写,意为“资源获取即初始化”。它是 C++ 之父 Bjarne Stroustrup 提出的设计理念,其核心是把资源和对象的生命周期绑定,对象创建获取资源,对象销毁释放资源。在 RAII 的指导下,C++ 把底层的资源管理问题提升到了对象生命周期管理的更高层次。

二、C++中的四种智能指针

1、auto_ptr(C++98 的方案,C11 已抛弃)采用所有权模式。

auto_ptr<std::string> p1 (new string ("hello"));
auto_ptr<std::string> p2;
p2 = p1; //auto_ptr 不会报错
p2 = p1; //auto_ptr 不会报错.

此时不会报错,p2 剥夺了 p1 的所有权,但是当程序运行时访问 p1 将会报错。所以 auto_ptr 的缺点是:存在潜
在的内存崩溃问题!

2、unique_ptr(替换 auto_ptr )
unique_ptr 实现独占式拥有或严格拥有概念,保证同时间内只有个智能指针可以指向该对象。它对于避免资
源泄露特别有用。
采用所有权模式,还是上面那个例子

unique_ptr<string> p3 (new string (auto));//#4
unique_ptr<string> p4;//#5
p4 = p3;//此时会报错

编译器认为 p4=p3 非法,避免了p3不再指向有效数据的问题。
因此,unique_ptr 比 auto_ptr 更安全。

unique_ptr简单代码实现

#include<iostream>

template<typename T>
class unique_ptr
{
private:
  T * ptr_;
public:
  unique_ptr(T * ptr=nullptr):ptr_(ptr){}
  ~unique_ptr()
  {
    if (ptr_) 
    {
      delete ptr_;
      ptr_ = nullptr;
    }
  }
  //禁止复制构造和赋值操作
  unique_ptr(const unique_ptr& ptr)=delete;
  unique_ptr& operator=(const unique_ptr& ptr)=delete;

  //移动构造函数
  unique_ptr(unique_ptr&& other)
  {
    ptr_=other.ptr_;
    other.ptr_=nullptr;
  }
  //移动赋值操作
  unique_ptr& operator=(unique_ptr&& other)
  {
    if(this != &other)
    {
      delete ptr_;
      ptr_ = other.ptr_;
      other.ptr_ = nullptr;
    }
    return *this;
  }

  T& operator*() const {return * ptr_;}
  T* operator->() const {return ptr_;}
  T* get() const {return ptr_;}
  T* release()
  {
    T* p = ptr_;
    ptr_ = nullptr;
    return p;
  }
  void reset(T* p = nullptr)
  {
    delete ptr_;
    ptr_ = p;
  }
};

3、shared_ptr(共享型,强引用)

shared_ptr 实现共享式拥有概念,多个智能指针可以指向相同对象,该对象和其相关资源会在“最后一个引用被销
毁”时候释放。从名字 share 就可以看出了资源可以被多个指针共享,它使用计数机制来表明资源被几个指针共
享。
可以通过成员函数 use_count() 来查看资源的所有者个数,除了可以通过 new 来构造,还可以通过传入auto_ptr,
unique_ptr,weak_ptr 来构造。当我们调用release() 时,当前指针会释放资源所有权,计数减一。当计数等于 0
时,资源会被释放。
shared_ptr 是为了解决 auto_ptr 在对象所有权上的局限性 (auto_ptr 是独占的),在使用引用计数的机制上提供了
可以共享所有权的智能指针。

shared_ptr简单代码实现

#include<iostream>

template<typename T>
class share_ptr
{
private:
  T* ptr_;
  int *refCount;
public:
  share_ptr(T* ptr = nullptr):ptr_(ptr),refCount(new int(1)){}
  ~share_ptr()
  {
    if (--(*refCount) == 0){
      delete ptr_;
      delete refCount;
    }
  }

  share_ptr(const share_ptr& other):ptr_(other.ptr_),refCount(other.refCount)
  {
    ++(*refCount);
  }

  share_ptr& operator=(const share_ptr& other)
  {
    if (this != &other) {
      if (--(*refCount) == 0) {
        delete ptr_;
        delete refCount;
      }
      ptr_ = other.ptr_;
      refCount = other.refCount;
      ++(*refCount);
    }
    return *this;
  }

  T& operator*() const {return *ptr_;}
  T* operator->() const {return ptr_;}
  T* get() const {return ptr_;}
  int use_count() const {return *refCount;}
};

int main()
{
  share_ptr<int> sp1(new int(1));
  share_ptr<int> sp2(sp1);
  *sp1 = 10;
  *sp2 = 20;
  std::cout << sp1.use_count() << std::endl; //2
  std::cout << *sp1 << std::endl; //20
  std::cout << *sp2 << std::endl; //20

  share_ptr<int> sp3(new int(1));
  share_ptr<int> sp4(new int(2));
  sp3 = sp4;
  std::cout << sp3.use_count() << std::endl; //2
  return 0;
}

4、weak_ptr(弱引用)
weak_ptr是一种不控制对象生命周期的智能指针,它指向一个shared_ptr管理的对象。进行该对象的内存管理
的是那个强引用的shared_ptr。
weak_ptr 只是提供了对管理对象的一个访问手段。weak_ptr设计的目的是为配合 shared_ptr 而引入的一种智
能指针来协助 shared_ptr 工作,它只可以从一个 shared_ptr或另一个weak_ptr对象构造,它的构造和析构不会
引起引用记数的增加或减少。
weak_ptr是用来解决shared_ptr相互引用时的死锁问题,如果说两个shared_ptr相互引用,那么这两个指针的
引用计数永远不可能下降为0,也就是资源永远不会释放。它是对对象的⼀种弱引用,不会增加对象的引用计数,
和shared_ptr之间可以相互转化,shared_ptr 可以直接赋值给它,它可以通过调用lock 函数来获得shared_ptr。
当两个智能指针都是 shared_ptr 类型的时候,析构时两个资源引用计数会减一,但是两者引用计数还是为 1,导
致跳出函数时资源没有被释放(的析构函数没有被调用),解决办法:把其中一个改为weak_ptr就可以。

weak_ptr简单代码实现

#include<iostream>
#include<memory>

template<typename T>

class weak_ptr
{
private:
  T* ptr_;
public:
  weak_ptr():ptr_(nullptr){}
  weak_ptr(const std::shared_ptr<T>& p):ptr_(p.get()){}
  ~weak_ptr() {ptr_=nullptr;}
  weak_ptr(const weak_ptr<T>& other):ptr_(other.ptr_){}
  weak_ptr<T>& operator=(const weak_ptr<T>& other)
  {
    ptr_ = other.ptr_;
    return *this;
  }
  T& operator*() const {return *ptr_;}
  T* operator->() const {return ptr_;}
};

int main()
{
  std::shared_ptr<int> p(new int(1));
  std::weak_ptr<int> p2(p);
  

  return 0;
}

三、智能指针问题

1、shared_ptr循环引用问题
shared_ptr的循环引用问题在一些特定的场景下才会产生。

#include<iostream>
#include<memory>
class B;

class A
{
public:
  std::shared_ptr<B> m_b = nullptr;
};

class B
{
public:
  std::shared_ptr<A> m_a = nullptr;
};

int main()
{
  std::shared_ptr<A> a = std::make_shared<A>();
  std::shared_ptr<B> b = std::make_shared<B>();
  a->m_b = b;
  b->m_a = a;
  return 0;
}

根据代码执行顺序,share_ptr指针指向new创建的一个A对象,引用计数为1,同理,B指针也指向了堆空间的B对象,引用计数亦为1。

接下来,A对象里的成员m_a指向B对象,B对象的引用计数加1后为2,B对象的m_a也指向A对象,A对象引用计数也加1为2。

若此时代码执行结束,栈空间上的B指针先进行释放,B对象的引用计数减1后为1,后释放A指针,A对象的引用计数也减为1。由于A对象和B对象都是建立再堆空间上,两者相互依赖,都在等待对方释放。

可以看到,这个例子中,A对象 与 B对象互相使用着,导致双方的 shared_ptr 强引用数量不会为0,所以不会自动释放内存,产生了内存泄漏。

weak_ptr是C++11中引入的智能指针,weak_ptr不是用来管理资源的释放的,它主要是用来解决shared_ptr的循环引用问题的。

2、为什么要使用std::make_shared它执行了哪些步骤

在C++中,std::make_shared是一个方便的函数模板,用于创建std::shared_ptr指向的对象。它在对象的分配和构造过程中执行了以下步骤:

分配内存:make_shared会分配足够的内存以存储对象和用于引用计数的控制块。通常,这是通过调用全局的operator new来完成的。

构造对象:make_shared使用传递给它的参数构造对象。它会调用对象的构造函数,并传递相应的参数。这意味着你可以直接将参数传递给make_shared,而不需要显式地调用对象的构造函数。

创建控制块:make_shared会创建一个控制块,其中包含指向对象的指针和一个引用计数。该控制块还可能包含其他信息,如自定义的析构函数、内存分配器等。控制块是std::shared_ptr内部用于管理资源和跟踪引用计数的数据结构。

返回std::shared_ptr:最后,make_shared返回一个std::shared_ptr对象,该对象拥有指向刚刚分配的内存和控制块的所有权。这个std::shared_ptr可以被赋值给其他std::shared_ptr,或者被转移给其他对象,以共享对同一对象的所有权。

通过使用std::make_shared,可以将对象的分配和构造合并为单个操作,这有助于提高性能和内存使用效率。此外,使用make_shared还可以避免由于多次分配和引用计数操作引起的潜在错误,并提供了更强的异常安全性。因此,在大多数情况下,建议优先使用std::make_shared来创建std::shared_ptr指向的对象。

3、shared_ptr线程安全

std::shared_ptr本身并不是线程安全的。多个线程同时对同一个std::shared_ptr进行读写操作可能会导致数据竞争和未定义的行为。

然而,C++11引入了原子操作的概念,并且std::shared_ptr的引用计数是使用原子操作进行操作的。这意味着当多个线程同时对引用计数进行增减操作时,操作是原子的,不会导致数据竞争。

尽管引用计数的操作是线程安全的,但是对于使用std::shared_ptr所管理的对象的访问并不是自动线程安全的。如果多个线程同时访问共享的对象,并且至少有一个线程进行写操作,就可能导致竞态条件和数据损坏。在这种情况下,你需要使用其他机制,如互斥锁或原子操作,来确保对共享对象的访问是线程安全的。

如果你需要在多线程环境中使用std::shared_ptr,建议采取适当的同步措施,例如使用互斥锁或使用线程安全的数据结构,以确保对std::shared_ptr和相关对象的访问是线程安全的。

  • 2
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值