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;
}