深圳网站建设制作,浙江网站建设情况,简洁自适应wordpress主题,angularjs 网站开发RAII与智能指针 1.RAII1.1RAII理解1.2RAII的原理1.2.1简单的例子说明局部对象的自动销毁的特性 1.2.2 RAII 过程 2.智能指针2.1 auto_ptr2.1.1auto_ptr的使用构造函数与析构函数拷贝构造函数与赋值提领操作auto ptr其它函数 2.1.2autoptr使用的注意事项 2.2 unique_ptr2.2.1uni… RAII与智能指针 1.RAII1.1RAII理解1.2RAII的原理1.2.1简单的例子说明局部对象的自动销毁的特性 1.2.2 RAII 过程 2.智能指针2.1 auto_ptr2.1.1auto_ptr的使用构造函数与析构函数拷贝构造函数与赋值提领操作auto ptr其它函数 2.1.2autoptr使用的注意事项 2.2 unique_ptr2.2.1unique_ptr的使用unique_ptr的创建unique_ptr不能进行拷贝构造和赋值操作unique_ptr可以进行移动构造和移动赋值操作unique_ptr虽然没有拷贝操作但是可以从函数中返回unique_ptr 2.2.2unique_ptr使用场景 2.3 shared_ptr共享指针2.3.1 shared_ptr的使用shared_ptr的创建shared_ptr的访问shared_ptr的拷贝和赋值操作shared_ptr的引用计数 2.3.2 shared_ptr的线程安全2.3.3 shared_ptr与unordered map使用 2.4 weak_ptr2.4.1 weak_ptr理解2.4.2 为什么会有weak_ptr2.3.3 weak_ptr使用创建weak_ptr实例判断weak_ptr指向对象是否存在weak_ptr使用 2.5 几种智能指针的比较 1.RAII
1.1RAII理解
RAII(Resource Acquisition ls lnitialization) 资源获取即初始化是由 c之父 Biarne Stroustrup 提出的。 使用局部对象来管理资源的技术称为资源获取即初始化这里的资源主要是指操作系统中有限的东西如内存、网络套接字互斥量文件句柄等等局部对象是指存储在栈的对象它的生命周期是由操作系统来管理的无需人工介入。
1.2RAII的原理
资源的使用一般经历三个步骤
获取资源(创建对象)使用资源销毁资源 (析构对象)。
但是资源的销毁往往是程序员经常忘记的一个环节所以程序界就想如何在程序员中让资源自动销毁呢?解决问题的方案是: RAIl它充分的利用了 C语言局部对象自动销毁的特性来控制资源的生命周期。
1.2.1简单的例子说明局部对象的自动销毁的特性
#include iostream
#include stringusing namespace std;class Student
{
private:const string s_name;int s_age;
public:Student(const string name ,int age 0):s_name(name),s_age(age){coutConstruct a Studentendl;}~Student(){coutDestory a Studentendl;}
};int main(){Student stu1; #局部对象return 0;
}从 Student类可以看出当我们在 main 函数中声明一个局部对象的时候会自动调用构造函数进行对象的初始化当整个 main 函数执行完成后自动调用析构函数来销毁对象整个过程无需人工介入由操作系统自动完成。 于是基于上述实现方式可以想到当我们在使用资源的时候在构造函数中进行初始化在析构函数中进行销毁。
1.2.2 RAII 过程
设计一个类封装资源在构造函数中初始化在析构函数中执行销毁操作使用时声明一个该类的对象
2.智能指针
智能指针其实是将指针进行了封装可以像普通指针一样进行使用同时可以自行进行释放避免忘记释放指针指向的内存地址造成内存泄漏。
C11 里面的四个智能指针:
auto ptr,unique ptrshared ptr,weak ptr 下面对这四个智能指针进行一一介绍
2.1 auto_ptr
auto_ptr是较早版本的智能指针已经被 C11 弃用C98 中 auto_ptr 所做的事情就是动态分配对象以及当对象不再需要时自动执行清理。
2.1.1auto_ptr的使用
构造函数与析构函数
auto_ptr 在构造时获取对某个对象的所有权(ownership),在析构时释放该对象提高代码安全性因为我们不必关系应该如何释放auto ptr也不用担心发生异常时会有内存泄漏。
int *p new int(10);auto_ptrint ap(p);注意点
auto ptr 析构的时候会删除他所拥有的那个对象所以我们要注意两个auto_ptr 不能同时拥有同一个对象。
int *p new int(10);auto_ptrint ap1(p);
auto_ptrint ap2(p);因为 ap1 与 ap2 都认为指针 p 是归它管的在析构时都试图删除 p两次删除同一个对象的行为在C标准中是未定义的。所以我们必须防止这样使用 auto ptr
不应该用 auto ptr 来管理一个数组指针
string * sar new string[10];auto_ptrstring ap3(sar);构造函数的 explicit 关键词有效阻止从一个“裸指针隐式转换成 auto ptr 类型
拷贝构造函数与赋值
auto ptr 要求其对“裸”指针的完全占有性。也就是说一个“裸”指针不能同时被两个以上的 auto ptr 所拥有。那么在拷贝构造或赋值操作时就必须作特殊的处理来保证这个特性。 auto ptr的做法是“所有权转移”即拷贝或赋值的源对象将失去对“裸”指针的所有权所以与一般拷贝构造函数赋值承数不同auto ptr 的拷贝构造函数赋值函数的参数为引用而不是常引用(const reference). 当然一个 auto ptr 也不能同时拥有两个以上的“裸”指针所以拷贝或赋值的目标指针将先释放其所拥有的对象然后管理旧指针的资源因此旧指针即指向nullptr在访问旧指针时会出现悬空现象。 注意点:
auto ptr 被拷贝或被赋值后其已经失去对原对象的所有权这个时候对这个 auto ptr 的提领(dereference)操作是不安全的。
int *p new int(10);
auto_ptrint ap1(p);
auto_ptrint ap2 ap1;cout*ap1endl; //error此时ap1已经失去对p指针的拥有权将 auto_ptr 作为函数参数按值传递时函数调用过程中在函数的作用域中会产生一个局部对象来接收传入的 auto ptr(拷贝构造)这样传入的实参 auto ptr 就失去了其对原对象的所有权而该对象会在函数退出时被局部 auto ptr 删除。
void fun(auto_ptrint ap)
{cout *ap endl;
}int main(){auto_ptrint ap1(new int(10));fun(ap1);cout*ap1endl; //error经过fun(ap1)函数调用ap1已经不再拥有任何对象了return 0;
}这种情况容易出错所以 auto ptr 作为函数参数按值传递是一定要避免的。或许用auto ptr 的指针或引用作为函数参数或许可以但是我们并不知道在函数中对传入的 auto ptr 做了什么如果当中某些操作使其失去了对对象的所有权那么这还是可能会导致致命的执行期错误。也许用 const reference 的形式来传递 auto ptr 会是一个不错的选择。
auto ptr在基类和子类隐式转换
class Object{ };class Base: public Object{ };auto_ptrObject apobj auto_ptrBase(new Base);提领操作
提领操作有两个操作
返回其所拥有的对象的引用实现了通过 auto ptr 调用其所拥有的对象的成员首先要确保这个智能指针确实拥有某个对象否则这个操作的行为即对空指针的提领是未定义。
auto ptr其它函数
get 用来显式的返回 auto ptr 所拥有的对象指针。我们可以发现标准库提供的 auto ptr 既不提供从裸”指针到auto _ptr 的隐式转换(构造函数为 explicit),也不提供 auto ptr 到指针的隐式转换从使用上来讲可能不那么的灵活考虑到其所带来的安全性还是值得的。release,用来转移所有权。reset用来接收所有权如果接收所有权的 auto ptr 如果已经拥有某对象必须先释放该对象.
2.1.2autoptr使用的注意事项
auto ptr 不能指向数组auto_ptr 不能共享所有权auto_ptr 不能通过复制操作来初始化auto_ptr 不能放入容器中使用auto ptr 不能作为容器的成员
2.2 unique_ptr
C11 中使用 unique_ptr 替代auto_ptr unique 是独特的、唯一的意思故名思议unique ptr 可以“独占”地拥有它所指向的对象是一种定义在memory中的智能指针(smart pointer)保证一个对象同一时间只有一个智能指针。 unique_ptr 对象中保存指向某个对象的指针当它本身被删除或者离开其作用域时会自动释放其指向对象所占用的资源。
2.2.1unique_ptr的使用
unique_ptr的创建
要想创建一个 unique ptr需要将一个 new 操作符返回的指针传递给 unique ptr 的构造函数.
int main()
{ unique_ptrint pt(new int(10));cout *pt endl; //10return 0;
}unique_ptr不能进行拷贝构造和赋值操作
unique_ptr operator( const unique_ptr ) delete;unique_ptr可以进行移动构造和移动赋值操作
unique ptr 虽然没有支持普通的拷贝和赋值操作但却提供了一种移动机制来将指针的所有权从一个 unique ptr转移给另一个 unique ptr。如果需要转移所有权可以使用 std:.move()函数
int main(){unique_ptrint pt1(new int(10));cout *pt1 endl; //10unique_ptrint pt2(move(pt1)); cout--------------endl;//cout *pt1 endl; //出错为空cout *pt2 endl; //10unique_ptrint pt3 move(pt2); cout~~~~~~~~~~~~~~~~endl;//cout *pt1 endl; //出错为空//cout *pt2 endl; //出错为空cout *pt3 endl; //10return 0;
} unique_ptr虽然没有拷贝操作但是可以从函数中返回unique_ptr
unique_ptrint clone(int a)
{unique_ptrint ptr(new int(a));return ptr;//返回unique_ptr
}int main()
{int val 11;unique_ptrint pt1 clone(val);cout *pt1 endl; //11return 0;
}
2.2.2unique_ptr使用场景
为动态申请的资源提供异常安全保证
void fun_(int a)
{int *p new int(5);//.....抛出异常
}void fun(int a)
{unique_ptrint ptr(new int(a));//.....抛出异常}fun_是传统的写法:在动态申请内存后有可能接下来的代码由于抛出异常或者提前退出 (if 语句)而没有执行 delete 操作。 解决的方法是使用 unique ptr 来管理动态内存只要 unique ptr 指针创建成功其析构函数都会被调用。确保动态资源被释放。 2. 返回函数内动态申请资源的所有权
unique_ptrint fun(int a)
{unique_ptrint ptr(new int(a));return ptr;//返回unique_ptr
}int main()
{int a 12;unique_ptrint ret fun(a);cout *ret endl; //12//函数结束后自动释放资源return 0;
}在容器中保存指针
int main()
{vectorunique_ptrint vec;unique_ptrint pt(new int(5));vec.push_back(move(pt));for(int i 0;i vec.size();i){cout *vec[i] endl; // 5}return 0;
}管理动态数组
int main()
{ unique_ptrvectorint[] pt(new vectorint {1,2,3,4,5});for(int i 0;i pt.get()-size();i){coutpt.get()-at(i)endl; //1,2,3,4,5}return 0;
}2.3 shared_ptr共享指针
shared ptr 是一个引用计数的智能指针用于共享对象的所有权。也就是说它允许多个指针指向同一个对象并且维护了一个共享的引用计数器当这个对象所有的智能指针被销毁时引用计数器0就会自动进行回收。
class Object
{
private:int val;
public:Object(int x 0):val(0){cout Construct Object endl;}~Object(){cout Destory Object endl;}};int main()
{shared_ptrObject pobj(new Object(13));//指针引用对象的个数cout pobj: pobj.use_count()endl; //1shared_ptrObject pobj1 pobj;cout pobj: pobj.use_count()endl; //2cout pobj1: pobj1.use_count()endl; //2return 0;
}一方面跟 STL 中大多数容器类型一样shared ptr 也是模板类因此在创建 shared ptr 时需要指定其指向的类型。另一方面shared ptr 指针允许让多个该类型的指针共享同一堆分配对象。同时 shared ptr 使用经典的“引用计数”方法来管理对象资源每个 shared ptr 对象关联一个共享的引用计数。
2.3.1 shared_ptr的使用
shared_ptr的创建
调用 make shared 库函数该函数会在堆中分配一个对象并初始化最后返回指向此对象的share ptr 实例安全、高效先 new 出一个对象然后把其原始指针传递给 share ptr 的构造函数
shared_ptrint ptm make_sharedint(16);cout *ptm endl; //16shared_ptrint ptn(new int(15));cout *ptn endl; //15
shared_ptr的访问
解引用操作符*获得原始对象进而访问其各个成员指针访问符-来访问原始对象的各个成员。
shared_ptr的拷贝和赋值操作
对于 shared ptr 在拷贝和赋值时的行为是每个 shared ptr 都有一个关联的计数值通常称为引用计数。无论何时我们拷贝一个 shared ptr计数器都会加1。当我们将一个指针的对象交给另一个指针管理后其关联的引用计数就会减1。 例如当用一个 shared ptr 初始化另一个 shred ptr时或将它当做参数传递给一个函数以及作为函数的返回值时它所关联的计数器就会递增。 当我们给 shared ptr 赋予一个新值或是 shared ptr 被销(例如一个局部的 shared ptr 离开其作用域)时计数器就会递减。 shared ptr 对象的计数器变为 0它就会自动释放自己所管理的对象。
class Object
{
private:int val;
public:Object(int x 0):val(0){cout Construct Object endl;}~Object(){cout Destory Object endl;}};int main()
{shared_ptrObject pobj make_sharedObject(13);//指针引用对象的个数cout pobj: pobj.use_count()endl; //1shared_ptrObject pobj1 pobj;cout pobj: pobj.use_count()endl; //2cout pobj1: pobj1.use_count()endl; //2return 0;
}对比我们上面的代码可以看到: 接下来我们用 pob初始化 pobit1两者关联的引用计数值增加为 2。随后函数结束pObi和 PObi2 相继离开函数作用域相应的引用计数值分别自减 1 最后变为 0于是 Obiect 对象被自动释放(调用其析构函数)。
shared_ptr的引用计数
shared _ptr 提供了两个函数来检查其共享的引用计数值分别是
unique()函数用来测试该 shared ptr 是否是原始指针唯一拥有者也就是 use count()的返回值为 1时unique()返回 true否则返回 false。use count()函数该函数返回当前指针的引用计数值。值得注意的是 use count()函数能效率很低应该只把它用于测试或调试。
2.3.2 shared_ptr的线程安全
(shared_ptr) 的引用计数本身是线程安全(引用计数是原子操作)多个线程同时读同一个 shared ptr 对象是线程安全的。如果是多个线程对同一个 shared ptr 对象进行读和写则需要加锁多线程读写 shared_ptr 所指向的同一个对象不管是相同的 shared ptr 对象还是不同的 shared ptr 对象也需要加锁保护。
2.3.3 shared_ptr与unordered map使用
如果把 shared_ptr 放到 unordered set 中或者用于 unrdered map 的 key, 那么要小心 hash table 退化为链表。但是其 hash value 是 shared_ptr 隐式转换为 bool 的结果。也就是说如果不自定义 hash 函数那么unordered {set/map] 会退化为链表。 为什么要尽量使用 make shared()? 申请被管理对象以及引用计数的内存:调用适当的构造函数初始化对象返回shared_ptr。 为了节省一次内存分配原来 shared_ptr x(new Obiect (10) ; 需要为 Obiect对象 和 RefCnt 各分配次内存现在用 make shared() 的话可以一次分配一块足够大的内存供 Obiect 和 RefCnt 对象容身。不过Obiect 的构造函数所需的参数要传给 make shared()后者再传给 Obiect; : Obiect)这只有在 C11 里通过perfect forwarding(完美转发) 才能完美解决。
2.4 weak_ptr
2.4.1 weak_ptr理解
weak_ptr 是为了配合 shared ptr 而引入的一种智能指针它指向一个由 shared ptr 管理的对象而不影响所指对象的生命周期也就是将一个 weak_ptr 绑定到一个 shared_ptr 不会改变 shared_ ptr 的引用计数。 即weak_ptr是为了协助shared_ptr而出现的。它不能访问对象只能观测shared_ptr的引用计数防止出现死锁。 不论是否有 weak ptr 指向一旦最后一个指向对象的 shared_ptr 被销毁对象就会被释放。从这个角度看weak_ptr 更像是 shared_ptr 的一个助手而不是智能指针。
2.4.2 为什么会有weak_ptr
在出现了循环引用(或环形引用)的情况下shared_ptr。
class Child;class Parent
{
public:shared_ptrChild child;~Parent(){cout Destory Parent endl;}void Priant() const{cout Parent----------- endl;}
};class Child
{
public:shared_ptrParent parent;~Child(){cout Destory Child endl;}
};int main()
{shared_ptrParent parent make_sharedParent();shared_ptrChild child make_sharedChild();parent-child child;child-parent parent;child-parent-Priant();return 0;
}上面代码的运行结果只打印出”Parent-----------”而并没有打印出Destory Parent”或”Destory Child说明 Parent 和 Child 的析构函数并没有调用到。这是因为Parent 和 Child 对象内部具有各自指向对方的 shared_ptr加上 parent和 child 这两个shared_ptr说明每个对象的引用计数都是 2。当程序退出时即使 parent 和 child 被销毁也仅仅是导致引用计数变为了1因此并未销毁 Parent 和 Child 对象。
2.3.3 weak_ptr使用
创建weak_ptr实例
创建一个weak_ptr时需要用一个 shared_ptr 实例来初始化weak_ptr由于是弱共享weak_ptr的创建并不会影响 shared_ptr 的引用计数值
int main()
{shared_ptrint sp make_sharedint(5);cout sp.use_count() endl; //1weak_ptrint wp(sp);cout sp.use_count() endl; //1cout wp.use_count() endl; //1return 0;
}判断weak_ptr指向对象是否存在
weak_ptr 并不改变其所共享的 shared_ptr 实例的用计数那就可能存在 weak ptr 指向的对象被释放掉这种情况。这时我们就不能使用 weak ptr 直接访问对象。那么我们如何判断 weak ptr 指向对象是否存在呢?C中提供了 lock 函数来实现该功能。 如果对象存在lock(函数返回一个指向共享对象的 shared ptr否则返回一个空 shared ptr。
shared_ptrint sp make_sharedint(5);//sp nullptr;weak_ptrint wp(sp);if(shared_ptrint pa wp.lock()){cout *pa endl;
}
else{cout wp.expired() endl; weak_ptr 还提供了 expired0函数来判断所指对象是否已经被销毁cout *wp is nullptr endl;
}weak_ptr使用
weak_ptr 并没有重载 operator-和 operator 操作符因此不可直接通过 weak_ptr 使用对象典型的用法是调用其lock 函数来获得 shared_ptr 示例进而访问原始对象。 最后我们来看看如何使用 weak_ptr 来改造最前面的代码打破循环引用问题。
class Child;class Parent
{
public:weak_ptrChild child;~Parent(){cout Destory Parent endl;}void Priant() {cout Parent----------- endl;}
};class Child
{
public:weak_ptrParent parent;~Child(){cout Destory Child endl;}
};int main()
{shared_ptrParent parent1 make_sharedParent();shared_ptrChild child1 make_sharedChild();weak_ptrParent parent(parent1);weak_ptrChild child(child1);parent1-child child1;child1-parent parent1;//child1-parent.lock()-Priant();if(!child.expired()){child1-parent.lock()-Priant();}return 0;
}2.5 几种智能指针的比较
unique_ptr和 shared ptr 类型指针有很大的不同: shared ptr 允许多个指针指同一对象而 nique_ptr 在某时刻只能有一个指针指向该对象(两个 unique ptr 不能指向同一个对象)。 使用场景 如果程序要使用多个指向同一个对象的指针应该选择 shared_ptr 如果程序要使用一个指向一个对象的指针则可以使用 unique_ptr; 如果使用 new [] 分配内存应该选择 unique_ptr; 如果函数使用 new 分配内存并返回指向该内存的指针将其返回类型声明为 unique_ptr 是不错的选择。 智能指针实现原理建立所有权ownership概念。 auto_ptr 和 unique_ptr 的策略对于特定的对象只能有一个智能指针可拥有它这样只有拥有对象的智能指针的析构函数会删除该对象。然后让赋值操作转让所有权。但 unique_ptr 的策略更严格unique_ptr 能够在编译期识别错误。 shared_ptr 采用的策略。跟踪引用特定对象的智能指针计数这称为引用计数reference counting。例如赋值时计数将加 1而指针过期时计数将减 1. 仅当最后一个指针过期时才调用 delete。 线程安全 shared_ptr引用计数在手段上使用了 atomic 原子操作只要 shared_ptr 在拷贝或赋值时增加引用析构时减少引用就可以了。首先原子是线程安全的所有 shared_ptr 智能指针在多线程下引用计数也是安全的也就是说 shared_ptr 智能指针在多线程下传递使用时引用计数是不会有线程安全问题的。 但是指向对象的指针不是线程安全的使用 shared_ptr 智能指针访问资源不是线程安全的需要手动加锁解锁。智能指针的拷贝也不是线程安全的。