【c++】智能指针以及实现

1.auto_ptr(不要使用,auto_ptr是C++98的智能指针,C++11明确声明不再支持。)

最原始的智能指针。

auto_ptr具有以下缺陷:auto_ptr有拷贝语义,拷贝后源对象变得无效,这可能引发很严重的问题。由于 auto_ptr 基于【排他所有权模式】,这意味着:两个指针(同类型)不能指向同一个资源,复制或赋值都会改变资源的所有权。

复制auto_ptr对象时,把指针指传给复制出来的对象,原有对象的指针成员随后重置为nullptr。
使用auto_ptr要知道:
1. 智能指针不能共享指向对象的所有权
2. 智能指针不能指向数组。因为其实现中调用的是delete而非delete[]
3. 智能指针不能作为容器类的元素。


 2、unique_ptr(一种强引用指针)

它其实算是auto_ptr的翻版(都是独占资源的指针,内部实现也基本差不多).

但是unique_ptr的名字能更好的体现它的语义,而且在语法上比auto_ptr更安全(尝试复制unique_ptr时会编译期出错)

当你需要转移所有权,需要显式命令std::move,尽管转移所有权后 还是有可能出现原有指针调用(调用就崩溃)的情况。这个语法能强调你是在转移所有权,让你清晰的知道自己在做什么,从而不乱调用原有指针。 

void runGame(){
  std::unique_ptr<Base> b = new Base();
  std::unique_ptr<Base> b1 = b;//Error!编译期出错,不允许复制指针指向同一个资源。
  std::unique_ptr<Base> b2 = std::move(b);//转移所有权给b2.
  b->doSomething();//Oops!b指向nullptr,运行期崩溃
}

3、shared_ptr(一种强引用指针)
 

多个shared_ptr指向同一处资源,当所有shared_ptr都全部释放时,该处资源才释放。
每个shared_ptr都占指针的两倍空间,一个装着原始指针,一个装着计数区域(SharedPtrControlBlock)的指针

(用原始指针构造时,会new一个SharedPtrControlBlock出来作为计数存放的地方,然后用指针指向它,计数加减都通过SharedPtrControlBlock指针间接操作。)

//shared计数放在这个结构体里面,实际上结构体里还应该有另一个weak计数。下文介绍weak_ptr时会解释。
struct SharedPtrControlBlock{
  int shared_count;
};
 
//大概长这个样子(化简版)
template<class T>
class shared_ptr{
  T* ptr;
  SharedPtrControlBlock* count;
};

 每次复制,多一个共享同处资源的shared_ptr时,计数+1。每次释放shared_ptr时,计数-1。
当shared计数为0时,则证明所有指向同一处资源的shared_ptr们全都释放了,则随即释放该资源(还会释放new出来的SharedPtrControlBlock)。

缺陷:模型循环依赖(互相引用或环引用)时,计数会不正常

//假如有这么一个怪物模型,它有2个亲人关系
 
class Monster{
  std::shared_ptr<Monster> m_father;
  std::shared_ptr<Monster> m_son;
public:
  void setFather(std::shared_ptr<Monster>& father);/
  void setSon(std::shared_ptr<Monster>& son); 
  ~Monster(){std::cout << "A monster die!";} 
};
//然后执行下面函数

void runGame(){
    std::shared_ptr<Monster> father = new Monster();
    std::shared_ptr<Monster> son = new Monster();
    father->setSon(son);
    son->setFather(father);
}

猜猜执行完runGame()函数后,这对怪物父子能正确释放(发出死亡的悲鸣)吗?
答案是不能。
开始:
father,son指向的堆对象 shared计数都是为2,son智能指针退出栈:son指向的堆对象 计数减为1,father指向的堆对象 计数仍为2。father智能指针退出栈:father指向的堆对象 计数减为1 , son指向的堆对象 计数仍为1。

函数结束:所有计数都没有变0,也就是说中途没有释放任何堆对象。为了解决这一缺陷的存在,出现弱引用指针weak_ptr。

4、weak_ptr(一种弱引用指针)
 

weak_ptr只有某个对象的访问权,而没有它的生命控制权 即是 弱引用,所以weak_ptr是一种弱引用型指针,不控制所指向对象生存期的智能指针,它指向一个由shared_ptr管理的对象。将一个weak_ptr绑定到一个shared_ptr不会改变shared_ptr的引用计数。)

主要是为了解决两个问题:一是循环引用问题,使得资源无法释放;例如 A 对象含有一个 shared_ptr<B>,而 B 对象也含有一个 shared_ptr<A>,那么很容易产生循环引用,使得内存无法释放。

悬挂指针(dangling pointer):指针指向的内存被删除;一个简单的场景是,A 线程创建资源,并传递给 B 线程,B 线程只读访问资源;但是 A 线程随后可能释放了资源,B 没有感知,而得到了一个悬挂指针。)

内部实现:
计数区域(SharedPtrControlBlock)结构体引进新的int变量weak_count,来作为弱引用计数。
每个weak_ptr都占指针的两倍空间,一个装着原始指针,一个装着计数区域的指针(和shared_ptr一样的成员)。
weak_ptr可以由一个shared_ptr或者另一个weak_ptr构造。
weak_ptr的构造和析构不会引起shared_count的增加或减少,只会引起weak_count的增加或减少。

被管理资源的释放只取决于shared计数,当shared计数为0,才会释放被管理资源,也就是说weak_ptr不控制资源的生命周期。

但是计数区域的释放却取决于shared计数和weak计数,当两者均为0时,才会释放计数区域。

//shared引用计数和weak引用计数
//之前的计数区域实际最终应该长这个样子
struct SharedPtrControlBlock{
  int shared_count;
  int weak_count;
};
 
//大概长这个样子(化简版)
template<class T>
class weak_ptr{
  T* ptr;
  SharedPtrControlBlock* count;
};

针对空悬指针问题:
空悬指针问题是指:无法知道指针指向的堆内存是否已经释放。

得益于引入的weak_count,weak_ptr指针可以使计数区域的生命周期受weak_ptr控制,

从而能使weak_ptr获取 被管理资源的shared计数,从而判断被管理对象是否已被释放。(可以实时动态地知道指向的对象是否被释放,从而有效解决空悬指针问题)

如果对象存在,lock()函数返回一个指向共享对象的shared_ptr(引用计数会增1),否则返回一个空shared_ptr。weak_ptr还提供了expired()函数来判断所指对象是否已经被销毁。

由于weak_ptr并没有重载operator ->和operator *操作符,因此不可直接通过weak_ptr使用对象,同时也没有提供get函数直接获取裸指针。典型的用法是调用其lock函数来获得shared_ptr示例,进而访问原始对象。
 

#include <thread>
#include <memory>
#include <iostream>
class T{
    public:
    T(int id): m_id(id){}
    int showid(){
        return m_id;
    }
    private:
    int m_id;
};

void threadtest(std::weak_str<T> t){
    std::this_thread::sleep_for(std::chrono::seconds(1));
    std::shared_ptr<T> sp = t.lock();
    if(sp)std::cout<<sp->showID();                      
}
int main()
{
  std::shared_ptr<T> sp = std::make_shared<T>(1);
  std::thread t2(threadtest, sp);
  t2.join();
  return 0;
}


 

猜你喜欢

转载自blog.csdn.net/ltd0924/article/details/129652519