【Smart_Point】C/C++ 中智能指针
C++11智能指针
目录
C++11智能指针
1.1 C++11智能指针介绍
1.2 为什么要使用智能指针
1.2.1 auto_ptr(C++98的方案,C++11已经抛弃)采用所有权模式。
1.2.2 unique_ptr
1.2.4 weak_ptr
1.3.1 Counter简单实现
1.3.3 weak_ptr简单实现
1.4 分清楚场合应该使用哪种类型的智能指针;
C++里面的四个智能指针: auto_ptr, unique_ptr,shared_ptr, weak_ptr 其中后三个是C++11支持,并且第一个已经被C++11弃用。
1.1 C++11智能指针介绍
智能指针主要用于管理在堆上分配的内存,它将普通的指针封装为一个栈对象。当栈对象的生存周期结束后,会在析构函数中释放掉申请的内存,从而防止内存泄漏。C++ 11中最常用的智能指针类型为shared_ptr,它采用引用计数的方法,记录当前内存资源被多少个智能指针引用。该引用计数的内存在堆上分配。当新增一个时引用计数加1,当过期时引用计数减一。只有引用计数为0时,智能指针才会自动释放引用的内存资源。对shared_ptr进行初始化时不能将一个普通指针直接赋值给智能指针,因为一个是指针,一个是类。可以通过make_shared函数或者通过构造函数传入普通指针。并可以通过get函数获得普通指针。
1.2 为什么要使用智能指针
智能指针的作用是管理一个指针,因为存在以下这种情况:申请的空间在函数结束时忘记释放,造成内存泄漏。使用智能指针可以很大程度上的避免这个问题,因为智能指针是一个类,当超出了类的实例对象的作用域时,会自动调用对象的析构函数,析构函数会自动释放资源。所以智能指针的作用原理就是在函数结束时自动释放内存空间,不需要手动释放内存空间。
1.2.1 auto_ptr
(C++98的方案,C++11已经抛弃)采用所有权模式。
auto_ptr<string> p1 (new string ("I reigned lonely as a cloud."));
auto_ptr<string> p2;
p2 = p1; //auto_ptr不会报错.
此时不会报错,p2
剥夺了p1
的所有权,但是当程序运行时访问p1
将会报错。所以auto_ptr
的缺点是:存在潜在的内存崩溃问题!
std::auto_ptr 真正让人容易误用的地方是其不常用的复制语义,即当复制一个 std::auto_ptr 对象时(拷贝复制或 operator = 复制),原对象所持有的堆内存对象也会转移给复制出来的对象。示例代码如下:
#include <iostream>
#include <memory>
int main()
{//测试拷贝构造std::auto_ptr<int> sp1(new int(8));std::auto_ptr<int> sp2(sp1);if (sp1.get() != NULL){std::cout << "sp1 is not empty." << std::endl;}else{std::cout << "sp1 is empty." << std::endl;}
if (sp2.get() != NULL){std::cout << "sp2 is not empty." << std::endl;}else{std::cout << "sp2 is empty." << std::endl;}
//测试赋值构造std::auto_ptr<int> sp3(new int(8));std::auto_ptr<int> sp4;sp4 = sp3;if (sp3.get() != NULL){std::cout << "sp3 is not empty." << std::endl;}else{std::cout << "sp3 is empty." << std::endl;}
if (sp4.get() != NULL){std::cout << "sp4 is not empty." << std::endl;}else{std::cout << "sp4 is empty." << std::endl;}
return 0;
}
上述代码中分别利用拷贝构造(sp1 => sp2)和 赋值构造(sp3 => sp4)来创建新的 std::auto_ptr 对象,因此 sp1 持有的堆对象被转移给 sp2,sp3 持有的堆对象被转移给 sp4。我们得到程序执行结果如下:
[root@iZ238vnojlyZ testx]# g++ -g -o test_auto_ptr test_auto_ptr.cpp
[root@iZ238vnojlyZ testx]# ./test_auto_ptr
sp1 is empty.
sp2 is not empty.
sp3 is empty.
sp4 is not empty.
由于 std::auto_ptr 这种不常用的复制语义,我们应该避免在 stl 容器中使用 std::auto_ptr,例如我们绝不应该写出如下代码:
std::vector<std::auto_ptr<int>> myvectors;
当用算法对容器操作的时候(如最常见的容器元素遍历),很难避免不对容器中的元素实现赋值传递,这样便会使容器中多个元素被置为空指针,这不是我们想看到的,会造成很多意想不到的错误。
以史为鉴,作为 std::auto_ptr 的替代者 std::unique_ptr 吸取了这个经验教训。下文会来详细介绍。
正因为 std::auto_ptr 的设计存在如此重大缺陷,C++11 标准在充分借鉴和吸收了 boost 库中智能指针的设计思想,引入了三种类型的智能指针,即 std::unique_ptr、std::shared_ptr 和 std::weak_ptr。
boost 还有 scoped_ptr,C++11 并没有全部照搬,而是选择了三个最实用的指针类型。在 C++11 中可以通过 std::unique_ptr 达到与 boost::scoped_ptr 一样的效果。
1.2.2 unique_ptr
(替换auto_ptr
)unique_ptr
实现独占式拥有或严格拥有概念,保证同一时间内只有一个智能指针可以指向该对象。它对于避免资源泄露(例如“以new创建对象后因为发生异常而忘记调用delete”)特别有用。 与shared_ptr不同,某一时刻,只能有一个unique_ptr指向一个给定的对象。因此,当unique_ptr被销毁,它所指的对象也会被销毁。
std::unique_ptr 对其持有的堆内存具有唯一拥有权,也就是说引用计数永远是 1,std::unique_ptr 对象销毁时会释放其持有的堆内存。可以使用以下方式初始化一个 std::unique_ptr 对象:
//初始化方式1
std::unique_ptr<int> sp1(new int(123));
//初始化方式2
std::unique_ptr<int> sp2;
sp2.reset(new int(123));
//初始化方式3
std::unique_ptr<int> sp3 = std::make_unique<int>(123);
你应该尽量使用初始化方式 3 的方式去创建一个 std::unique_ptr 而不是方式 1 和 2,因为形式 3 更安全,原因 Scott Meyers 在其《Effective Modern C++》中已经解释过了,有兴趣的读者可以阅读此书相关章节。
令很多人对 C++11 规范不满的地方是,C++11 新增了 std::make_shared() 方法创建一个 std::shared_ptr 对象,却没有提供相应的 std::make_unique() 方法创建一个 std::unique_ptr 对象,这个方法直到 C++14 才被添加进来。当然,在 C++11 中你很容易实现出这样一个方法来:
template<typename T, typename... Ts>
std::unique_ptr<T> make_unique(Ts&& ...params)
{return std::unique_ptr<T>(new T(std::forward<Ts>(params)...));
}
鉴于 std::auto_ptr 的前车之鉴,std::unique_ptr 禁止复制语义,为了达到这个效果,std::unique_ptr 类的拷贝构造函数和赋值运算符(operator =)被标记为 delete。
template <class T>
class unique_ptr
{//省略其他代码...
//拷贝构造函数和赋值运算符被标记为deleteunique_ptr(const unique_ptr&) = delete;unique_ptr& operator=(const unique_ptr&) = delete;
};//禁止拷贝复制和operattor=复制,允许所有权转移
因此,下列代码是无法通过编译的:
std::unique_ptr<int> sp1(std::make_unique<int>(123));;
//以下代码无法通过编译
//std::unique_ptr<int> sp2(sp1);
std::unique_ptr<int> sp3;
//以下代码无法通过编译
//sp3 = sp1;
禁止复制语义也存在特例,即可以通过一个函数返回一个 std::unique_ptr:
#include <memory>std::unique_ptr<int> func(int val)
{std::unique_ptr<int> up(new int(val));return up;
}int main()
{std::unique_ptr<int> sp1 = func(123);return 0;
}
上述代码从 func 函数中得到一个 std::unique_ptr 对象,然后返回给 sp1。
既然 std::unique_ptr 不能复制,那么如何将一个 std::unique_ptr 对象持有的堆内存转移给另外一个呢?答案是使用移动构造,示例代码如下
#include <memory>int main()
{std::unique_ptr<int> sp1(std::make_unique<int>(123));std::unique_ptr<int> sp2(std::move(sp1));std::unique_ptr<int> sp3;sp3 = std::move(sp2);return 0;
}
以上代码利用 std::move 将 sp1 持有的堆内存(值为 123)转移给 sp2,再把 sp2 转移给 sp3。最后,sp1 和 sp2 不再持有堆内存的引用,变成一个空的智能指针对象。并不是所有的对象的 std::move 操作都有意义,只有实现了移动构造函数(Move Constructor)或移动赋值运算符(operator =)的类才行,而 std::unique_ptr 正好实现了这二者,以下是实现伪码:
template<typename T, typename Deletor>
class unique_ptr
{//其他函数省略...
public:unique_ptr(unique_ptr&& rhs){this->m_pT = rhs.m_pT;//源对象释放rhs.m_pT = nullptr;}unique_ptr& operator=(unique_ptr&& rhs){this->m_pT = rhs.m_pT;//源对象释放rhs.m_pT = nullptr;return *this;}private:T* m_pT;
};
这是 std::unique_ptr 具有移动语义的原因。
std::unique_ptr 不仅可以持有一个堆对象,也可以持有一组堆对象,示例如下:
#include <iostream>
#include <memory>int main()
{//创建10个int类型的堆对象//形式1std::unique_ptr<int[]> sp1(new int[10]);//形式2std::unique_ptr<int[]> sp2;sp2.reset(new int[10]);//形式3std::unique_ptr<int[]> sp3(std::make_unique<int[]>(10));for (int i = 0; i < 10; ++i){sp1[i] = i;sp2[i] = i;sp3[i] = i;}for (int i = 0; i < 10; ++i){std::cout << sp1[i] << ", " << sp2[i] << ", " << sp3[i] << std::endl;}return 0;
}
程序执行结果如下:
0, 0, 0
1, 1, 1
2, 2, 2
3, 3, 3
4, 4, 4
5, 5, 5
6, 6, 6
7, 7, 7
8, 8, 8
9, 9, 9
std::shared_ptr 和 std::weak_ptr 也可以持有一组堆对象,用法与 std::unique_ptr 相同,下文不再赘述。
采用所有权模式,还是上面那个例子
unique_ptr<string> p3 (new string ("auto")); //#4
unique_ptr<string> p4; //#5
p4 = p3;//此时会报错!!
编译器认为p4=p3
非法,避免了p3
不再指向有效数据的问题。尝试复制p3
时会编译期出错,而auto_ptr
能通过编译期从而在运行期埋下出错的隐患。因此,unique_ptr
比auto_ptr
更安全。
另外unique_ptr
还有更聪明的地方:当程序试图将一个 unique_ptr
赋值给另一个时,如果源 unique_ptr
是个临时右值,编译器允许这么做;如果源 unique_ptr
将存在一段时间,编译器将禁止这么做,比如:
unique_ptr<string> pu1(new string ("hello world"));
unique_ptr<string> pu2;
pu2 = pu1; // #1 不允许
unique_ptr<string> pu3;
pu3 = unique_ptr<string>(new string ("You")); // #2 允许
其中#1留下悬挂的unique_ptr(pu1)
,这可能导致危害。而#2不会留下悬挂的unique_ptr
,因为它调用 unique_ptr
的构造函数,该构造函数创建的临时对象在其所有权让给 pu3
后就会被销毁。这种随情况而已的行为表明,unique_ptr
优于允许两种赋值的auto_ptr
。
注:如果确实想执行类似与#1的操作,要安全的重用这种指针,可给它赋新值。C++有一个标准库函数std::move()
,让你能够将一个unique_ptr
赋给另一个。尽管转移所有权后 还是有可能出现原有指针调用(调用就崩溃)的情况。但是这个语法能强调你是在转移所有权,让你清晰的知道自己在做什么,从而不乱调用原有指针。
额外:boost库的boost::scoped_ptr
也是一个独占性智能指针,但是它不允许转移所有权,从始而终都只对一个资源负责,它更安全谨慎,但是应用的范围也更狭窄。
unique_ptr<string> ps1, ps2;
ps1 = demo("hello");
ps2 = move(ps1);
ps1 = demo("alexia");
cout << *ps2 << *ps1 << endl;
1.2.3 shared_ptr
shared_ptr
实现共享式拥有概念。多个智能指针可以指向相同对象,该对象和其相关资源会在“最后一个引用被销毁”时候释放。从名字share就可以看出了资源可以被多个指针共享,它使用计数机制来表明资源被几个指针共享。可以通过成员函数use_count()来查看资源的所有者个数。除了可以通过new来构造,还可以通过传入auto_ptr
, unique_ptr
,weak_ptr
来构造。当我们调用release()时,当前指针会释放资源所有权,计数减一。当计数等于0时,资源会被释放。
td::unique_ptr 对其持有的资源具有独占性,而 std::shared_ptr 持有的资源可以在多个 std::shared_ptr 之间共享,每多一个 std::shared_ptr 对资源的引用,资源引用计数将增加 1,每一个指向该资源的 std::shared_ptr 对象析构时,资源引用计数减 1,最后一个 std::shared_ptr 对象析构时,发现资源计数为 0,将释放其持有的资源。多个线程之间,递增和减少资源的引用计数是安全的。(注意:这不意味着多个线程同时操作 std::shared_ptr 引用的对象是安全的)。std::shared_ptr 提供了一个 use_count() 方法来获取当前持有资源的引用计数。除了上面描述的,std::shared_ptr 用法和 std::unique_ptr 基本相同。
shared_ptr
是为了解决 auto_ptr
在对象所有权上的局限性(auto_ptr
是独占的), 在使用引用计数的机制上提供了可以共享所有权的智能指针。
成员函数:
std::shared_ptr<int> sp1(new int(123));
//
std::shared_ptr<int> sp2;
sp2.reset(new int(123));
//
std::shared+ptr<int> sp3;
sp3=std::make_shared<int>(123);
和 std::unique_ptr 一样,你应该优先使用 std::make_shared 去初始化一个 std::shared_ptr 对象。
再来看另外一段代码:
#include <iostream>
#include <memory>class A
{
public:A(){std::cout << "A constructor" << std::endl;}~A(){std::cout << "A destructor" << std::endl;}
};int main()
{{//初始化方式1std::shared_ptr<A> sp1(new A());std::cout << "sp1 use count: " << sp1.use_count() << std::endl;//初始化方式2std::shared_ptr<A> sp2(sp1);std::cout << "sp2 use count: " << sp1.use_count() << std::endl;sp2.reset();std::cout << "sp2 reset use count: " << sp1.use_count() << std::endl;{std::shared_ptr<A> sp3 = sp1;std::cout << "sp3 = sp1 use count: " << sp1.use_count() << std::endl;}std::cout << "use count: " << sp1.use_count() << std::endl;}return 0;
}
上述代码 22 行 sp1 构造时,同时触发对象 A 的构造,因此 A 的构造函数会执行;
此时只有一个 sp1 对象引用 22 行 new 出来的 A 对象(为了叙述方便,下文统一称之为资源对象 A),因此代码 24 行打印出来的引用计数值为 1;
代码 27 行,利用 sp1 拷贝一份 sp2,导致代码 28 行打印出来的引用计数为 2;
代码 30 行调用 sp2 的 reset() 方法,sp2 释放对资源对象 A 的引用,因此代码 31 行打印的引用计数值再次变为 1;
代码 34 行 利用 sp1 再次 创建 sp3,因此代码 35 行打印的引用计数变为 2;
程序执行到 36 行以后,sp3 出了其作用域被析构,资源 A 的引用计数递减 1,因此 代码 38 行打印的引用计数为 1;
程序执行到 39 行以后,sp1 出了其作用域被析构,在其析构时递减资源 A 的引用计数至 0,并析构资源 A 对象,因此类 A 的析构函数被调用。
A constructor sp1 use count: 1 sp2 use count: 2 sp2 reset use count: 1 sp3 = sp1 use count: 2 use count: 1 A destructor
所以整个程序的执行结果如下:
use_count 返回引用计数的个数unique 返回是否是独占所有权( use_count 为 1)swap 交换两个 `shared_ptr` 对象(即交换所拥有的对象)reset 放弃内部对象的所有权或拥有对象的变更, 会引起原有对象的引用计数的减少get 返回内部对象(指针), 由于已经重载了()方法, 因此和直接使用对象是一样的.如
shared_ptr<int> sp(new int(1));
sp
与 sp.get()
是等价的。
share_ptr
的简单例子:
int main()
{string *s1 = new string("s1");shared_ptr<string> ps1(s1);shared_ptr<string> ps2;ps2 = ps1;cout << ps1.use_count()<<endl; //2cout<<ps2.use_count()<<endl; //2cout << ps1.unique()<<endl; //0string *s3 = new string("s3");shared_ptr<string> ps3(s3);cout << (ps1.get()) << endl; //033AEB48cout << ps3.get() << endl; //033B2C50swap(ps1, ps3); //交换所拥有的对象cout << (ps1.get())<<endl; //033B2C50cout << ps3.get() << endl; //033AEB48cout << ps1.use_count()<<endl; //1cout << ps2.use_count() << endl; //2ps2 = ps1;cout << ps1.use_count()<<endl; //2cout << ps2.use_count() << endl; //2ps1.reset(); //放弃ps1的拥有权,引用计数的减少cout << ps1.use_count()<<endl; //0cout << ps2.use_count()<<endl; //1
}
1.2.4 weak_ptr
share_ptr
虽然已经很好用了,但是有一点share_ptr
智能指针还是有内存泄露的情况,当两个对象相互使用一个shared_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
。
class B; //声明
class A
{
public:shared_ptr<B> pb_;~A(){cout << "A delete\n";}
};class B
{
public:shared_ptr<A> pa_;~B(){cout << "B delete\n";}
};void fun()
{shared_ptr<B> pb(new B());shared_ptr<A> pa(new A());cout << pb.use_count() << endl; //1cout << pa.use_count() << endl; //1pb->pa_ = pa;pa->pb_ = pb;cout << pb.use_count() << endl; //2cout << pa.use_count() << endl; //2
}int main()
{fun();return 0;
}
可以看到fun函数中pa ,pb
之间互相引用,两个资源的引用计数为2,当要跳出函数时,智能指针pa,pb
析构时两个资源引用计数会减1,但是两者引用计数还是为1,导致跳出函数时资源没有被释放(A、B的析构函数没有被调用)运行结果没有输出析构函数的内容,造成内存泄露。如果把其中一个改为weak_ptr
就可以了,我们把类A里面的shared_ptr
pb
,改为weak_ptr`
pb` ,运行结果如下:
1
1
1
2
B delete
A delete
这样的话,资源B的引用开始就只有1,当pb
析构时,B的计数变为0,B得到释放,B释放的同时也会使A的计数减1,同时pa析构时使A的计数减1,那么A的计数为0,A得到释放。
注意:我们不能通过weak_ptr
直接访问对象的方法,比如B对象中有一个方法print(),我们不能这样访问,pa->pb_->print()
,因为pb_
是一个weak_ptr
,应该先把它转化为shared_ptr
,如:
shared_ptr<B> p = pa->pb_.lock();
p->print();
weak_ptr
没有重载*和->但可以使用 lock 获得一个可用的 shared_ptr
对象. 注意, weak_ptr
在使用前需要检查合法性.
expired 用于检测所管理的对象是否已经释放, 如果已经释放, 返回 true; 否则返回 false.
lock 用于获取所管理的对象的强引用(shared_ptr
). 如果 expired 为 true, 返回一个空的 shared_ptr
; 否则返回一个 shared_ptr
, 其内部对象指向与 weak_ptr
相同.
use_count 返回与 shared_ptr
共享的对象的引用计数.
reset 将 weak_ptr
置空.
weak_ptr
支持拷贝或赋值, 但不会影响对应的 shared_ptr
内部对象的计数.
1.3 share_ptr
和weak_ptr
的核心实现
weak_ptr
的作为弱引用指针,其实现依赖于counter的计数器类和share_ptr
的赋值,构造,所以先把counter和share_ptr
简单实现
1.3.1 Counter简单实现
class Counter
{
public:Counter() : s(0), w(0){};int s; //share_ptr的引用计数int w; //weak_ptr的引用计数
};
counter对象的目地就是用来申请一个块内存来存引用基数,s是share_ptr
的引用计数,w是weak_ptr
的引用计数,当w为0时,删除Counter对象。
1.3.2 share_ptr
的简单实现
template <class T>
class WeakPtr; //为了用weak_ptr的lock(),来生成share_ptr用,需要拷贝构造用template <class T>
class SharePtr
{
public:SharePtr(T *p = 0) : _ptr(p){cnt = new Counter();if (p)cnt->s = 1;cout << "in construct " << cnt->s << endl;}~SharePtr(){release();}SharePtr(SharePtr<T> const &s){cout << "in copy con" << endl;_ptr = s._ptr;(s.cnt)->s++;cout << "copy construct" << (s.cnt)->s << endl;cnt = s.cnt;}SharePtr(WeakPtr<T> const &w)//为了用weak_ptr的lock(),来生成share_ptr用,需要拷贝构造用{cout << "in w copy con " << endl;_ptr = w._ptr;(w.cnt)->s++;cout << "copy w construct" << (w.cnt)->s << endl;cnt = w.cnt;}SharePtr<T> &operator=(SharePtr<T> &s){if (this != &s){release();(s.cnt)->s++;cout << "assign construct " << (s.cnt)->s << endl;cnt = s.cnt;_ptr = s._ptr;}return *this;}T &operator*(){return *_ptr;}T *operator->(){return _ptr;}friend class WeakPtr<T>; //方便weak_ptr与share_ptr设置引用计数和赋值protected:void release(){cnt->s--;cout << "release " << cnt->s << endl;if (cnt->s < 1){delete _ptr;if (cnt->w < 1){delete cnt;cnt = NULL;}}}private:T *_ptr;Counter *cnt;
};
share_ptr的给出的函数接口为:构造,拷贝构造,赋值,解引用,通过release来在引用计数为0的时候删除_ptr和cnt的内存。
1.3.3 weak_ptr简单实现
template <class T>
class WeakPtr
{
public: //给出默认构造和拷贝构造,其中拷贝构造不能有从原始指针进行构造WeakPtr(){_ptr = 0;cnt = 0;}WeakPtr(SharePtr<T> &s) : _ptr(s._ptr), cnt(s.cnt){cout << "w con s" << endl;cnt->w++;}WeakPtr(WeakPtr<T> &w) : _ptr(w._ptr), cnt(w.cnt){cnt->w++;}~WeakPtr(){release();}WeakPtr<T> &operator=(WeakPtr<T> &w){if (this != &w){release();cnt = w.cnt;cnt->w++;_ptr = w._ptr;}return *this;}WeakPtr<T> &operator=(SharePtr<T> &s){cout << "w = s" << endl;release();cnt = s.cnt;cnt->w++;_ptr = s._ptr;return *this;}SharePtr<T> lock(){return SharePtr<T>(*this);}bool expired(){if (cnt){if (cnt->s > 0){cout << "empty" << cnt->s << endl;return false;}}return true;}friend class SharePtr<T>; //方便weak_ptr与share_ptr设置引用计数和赋值protected:void release(){if (cnt){cnt->w--;cout << "weakptr release" << cnt->w << endl;if (cnt->w < 1 && cnt->s < 1){//delete cnt;cnt = NULL;}}}private:T *_ptr;Counter *cnt;
};
weak_ptr一般通过share_ptr来构造,通过expired函数检查原始指针是否为空,lock来转化为share_ptr。
不过感觉最后的SharePtr有个地方可以改进下: SharePtr在析构时释放_ptr和cnt,可能会导致释放cnt不成功。 代码里cnt的释放取决于cnt->w, 而cnt->w是否为0取决于WeakPtr的析构。 如果SharePtr先于WeakPtr析构, 此时cnt->w 大于0,SharePtr无法释放cnt
这个地方感觉可以不用判断cnt->w,直接delete cnt; cnt->s都为0了,不会有别的地方再使用管理的对象_ptr了,cnt的存在也就没意义了。
void release()
{cnt->s--;cout << "release " << cnt->s << endl;if (cnt->s < 1){delete _ptr;if (cnt->w < 1) //取消这个判断{delete cnt;cnt = NULL;}}
}
1.4 分清楚场合应该使用哪种类型的智能指针;
1.4.1 std::unique_ptr:
1 小巧、高速、具备只移型别的智能指针,对托管资源实施专属所有权语义。
2 默认地,资源析构采用delete运算符来实现,但可以指定自定义删除器。有状态的删除器和采用函数指针实现的删除器会增加std::unique_ptr型别的对象尺寸
3 将std::unique_ptr 转换成std::shared_ptr是容易实现的
1.4.2 std::shared_ptr:
1 提供方便的手段,实现了任意资源在共享所有权语义下进行生命周期管理的垃圾回收
2 与std::unique_*ptr* 相比,std::shared_ptr的尺寸通常是裸指针尺寸的两倍,它还会带来控制块的开销,并要求原子化的引用计数操作。
3 默认的资源析构通过delete运算符进行,但同时也支持定制删除器。删除器的型别对std::shared_ptr的型别没有影响。
4 避免使用裸指针型别的变量来创建 std::shared_ptr 指针
1.4.3 std::weak_ptr:
1 使用std::weakptr 来代替可能空悬的std::shared_ptr
2 std::weak_ptr 可能的用武之地包括缓存,观察者列表,以及避免std::shared_ptr 指针环路。
看看大师的总结,完美!!
具体到完全代替裸指针,恐怕也要再掂量掂量。
下面写一些劣势:
std::shared_ptr 会增加内存开销,复制的时候cpu消耗提高【原子count操作】
std::unique_ptr 内存占用小,几乎可以媲美裸指针;但是它毕竟是一个类,使用的时候,不能复制,导致你一个作用域内只能有一个可用的实例【类似rust的所有权吧,你用起来有点束手束脚】;
std::weak_ptr 必须跟std::shared_ptr配合使用。
优势:
省去你自己判断啥时候该释放资源【异步回调时候智能指针可以完美避免手动控制生命周期;enable_shared_frome_this 已经可以算是一种特别的编程技巧了】
裸指针的操作习惯
1.对于性能和内存使用有严格要求的场景,不要过于依赖智能指针【比如嵌入式这些的,实际上C+class就够了】
2.对于类型不敏感的数据【也就是内存了】,可以考虑使用std::array或者std::vector等。因为这个时候,你实际上就是C的操作,类型对于该内存仅仅起到一个布局描述的作用,频繁的类型转换【非继承关系】、字段偏移等操作,用智能指针也没有什么好处【因为你还是会拿到裸指针去操作】
3.其他的对类型敏感,或者对作用域敏感的数据内存,可以都考虑使用智能指针。局部作用域使用uniqe_ptr , 多作用域的使用shared_ptr,缓存可能失效的情况下使用weak_ptr。
一般应用,除了容器,几乎一上来全部使用uniqe_ptr,当需要抛出一个副本的时候,使用shared_ptr。当功能完成的时候,哪个内存是全局生命周期,改成裸指针【全局裸指针我都不判空】。如果该项目不是那么重要,甚至我都会全部用shared_ptr,不用关心性能问题,因为C++本身的编译性能已经很高了,随便写写性能就够了,只要不飞,内存泄漏不是问题。
当我要去判断某一个内存之后的操作会失效,但是不知道什么时候失效的时候,我使用weak_ptr和shared_ptr。通过weak_ptr接口可以线程安全的获取我之前的智能指针是否还生效。【这个时候,裸指针,几乎是没有办法的了,很容易出现野指针】
参考: https://blog.csdn.net/Darlingqiang/article/details/114065349?spm=1001.2014.3001.5501
https://zhuanlan.zhihu.com/p/29628938
https://www.zhihu.com/question/319277442/answer/1517987598
https://www.zhihu.com/question/319277442/answer/1517987598
https://blog.csdn.net/H_Strong/article/details/88754979?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522161544736616780274137097%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=161544736616780274137097&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~top_click~default-1-88754979.first_rank_v2_pc_rank_v29_10&utm_term=%E6%99%BA%E8%83%BD%E6%8C%87%E9%92%88
相关文章:

Linux 虚拟内存和物理内存的理解【转】
转自:http://www.cnblogs.com/dyllove98/archive/2013/06/12/3132940.html 首先,让我们看下虚拟内存: 第一层理解 1. 每个进程都有自己独立的4G内存空间,各个进程的内存空间具有类似的结构 2. 一个新进程建立的时候,…

直方图变换——查找
#include <opencv2/core/core.hpp> #include <opencv2/imgproc/imgproc.hpp> #include <opencv2/highgui/highgui.hpp> int main() {// 图像获取及验证cv::Mat srcImage cv::imread("..\\images\\flower3.jpg");if( !srcImage.data ) return 1;//…

【C++】C/C++ 中default/delete特性
C类的特殊成员函数及default/delete特性 本文内容侧重个人理解,深入理解其原理推荐https://www.geeksforgeeks.org 目录 目录 C类的特殊成员函数及default/delete特性 前言 1. 构造函数和拷贝构造函数 2. 拷贝赋值运算符 3. C11特性之default关键字(P237, P4…

Celery--任务调度利器
2019独角兽企业重金招聘Python工程师标准>>> Celery文档: http://docs.jinkan.org/docs/celery/getting-started/first-steps-with-celery.html 安装celery和celery-with-redis pip install Celery pip install celery-with-redis开始编写task.py # tasks.py import…

直方图变换——累计
#include <opencv2/core/core.hpp> #include <opencv2/imgproc/imgproc.hpp> #include <opencv2/highgui/highgui.hpp> int main() {// 图像获取及验证cv::Mat srcImage cv::imread("..\\images\\flower3.jpg");if( !srcImage.data ) return 1;//…

iOS开发经验总结,我的2019进阶之路!
4G改变了生活,抓住机会的人已经在这个社会有了立足之地,马上迎来5G的时代,你做好准备了吗!对于即将迎来的5G时代,无疑会是音视频的又一个高潮!那么作为程序员的我们,应该怎么样去迎接它呢~~ 改变…

【C++】C/C++ 中多态情形下的虚函数表查看方法
1.查看工具 找到VS2017命令提示符工具 选择“VS 2017的开发人员命令提示符” 点击该选项栏,弹出“VS 2017的开发人员命令提示符”窗口 cd 控制进入带查看类躲在的位置 使用命令:cl /d1 reportSingleClassLayoutXXX [filename],XXX表示类名&…

PHP中session_register函数详解用法
语法: boolean session_register(string name);注册新的变量。返回值: 布尔值函数种类: 资料处理内容说明本函数在全域变量中增加一个变量到目前的 Session 之中。参数 name 即为欲加入的变量名。成功则返回 true 值。假如在头文件,开启session,即使用session_start…

Fedora 提出统一流程,弃用上千 Python 2 软件包更可控
开发四年只会写业务代码,分布式高并发都不会还做程序员? >>> Fedora 社区正在讨论弃用 Python 2 软件包的统一流程。 https://pythonclock.org Python 2 将于 2020 年 1 月 1 日正式退休,官方不再提供维护,当前倒计时不…

【C++】C++对象模型:对象内存布局详解(C#实例)
C对象模型:对象内存布局详解 0.前言 C对象的内存布局、虚表指针、虚基类指针解的探讨,参考。 1.何为C对象模型? 引用《深度探索C对象模型》这本书中的话: 有两个概念可以解释C对象模型: 语言中直接支持面向对象程序设计的部分…

Mybatis插件原理和PageHelper结合实战分页插件(七)
今天和大家分享下mybatis的一个分页插件PageHelper,在讲解PageHelper之前我们需要先了解下mybatis的插件原理。PageHelper的官方网站:https://github.com/pagehelper/Mybatis-PageHelper一、Plugin接口mybatis定义了一个插件接口org.apache.ibatis.plugin.Intercept…

直方图反向投影
#include "opencv2/imgproc/imgproc.hpp" #include "opencv2/highgui/highgui.hpp" #include <iostream> using namespace cv; using namespace std; int main() {// 加载源图像并验证cv::Mat srcImage cv::imread("..\\images\\hand1.jpg&quo…

Java异常处理12条军规
摘要: 简单实用的建议。 原文:Java异常处理12条军规公众号:Spring源码解析Fundebug经授权转载,版权归原作者所有。 在Java语言中,异常从使用方式上可以分为两大类: CheckedExceptionUncheckedException在Ja…

【Smart_Point】C/C++ 中独占指针unique_ptr
1. 独占指针unique_ptr 目录 1. 独占指针unique_ptr 1.1 unique_ptr含义 1.2 C11特性 1.3 C14特性 1.1 unique_ptr含义 unique_ptr 是 C 11 提供的用于防止内存泄漏的智能指针中的一种实现,独享被管理对象指针所有权的智能指针。unique_ptr对象包装一个原始指…

ORA-01940无法删除当前已连接用户
原文地址:ORA-01940无法删除当前已连接用户作者:17361887941)查看用户的连接状况 select username,sid,serial# from v$session ------------------------------------------ 如下结果: username sid serial# ------…

距离变换扫描实现
#include <opencv2/imgproc/imgproc.hpp> #include <opencv2/core/core.hpp> #include <opencv2/highgui/highgui.hpp> #include <iostream> // 计算欧式距离 float calcEuclideanDistance(int x1, int y1, int x2, int y2) {return sqrt(…

『扩欧简单运用』
扩展欧几里得算法 顾名思义,扩欧就是扩展欧几里得算法,那么我们先来简单地回顾一下这个经典数论算法。 对于形如\(axbyc\)的不定方程,扩展欧几里得算法可以在\(O(log_2alog_2b)\)的时间内找到该方程的一组特解,或辅助\(gcd\)判断该…

【Smart_Point】C/C++ 中共享指针 shared_ptr
1. 共享指针 shared_ptr 目录 1. 共享指针 shared_ptr 1.1 共享指针解决的问题? 1.2 创建 shared_ptr 对象 1.3 分离关联的原始指针 1.4 自定义删除器 Deleter 1.5 shared_ptr 相对于普通指针的优缺点 1.6 创建 shared_ptr 时注意事项 1.1 共享指针解决的问…

对数变换的三种实现方法
#include <opencv2/core/core.hpp> #include <opencv2/highgui/highgui.hpp> #include <opencv2/imgproc/imgproc.hpp> #include <iostream> using namespace cv; // 对数变换方法1 cv::Mat logTransform1(cv::Mat srcImage, int c) {// 输…

eclipse编辑窗口不见了(打开左边的java、xml文件,中间不会显示代码)
转自:https://blog.csdn.net/u012062810/article/details/46729779?utm_sourceblogxgwz4 1. windows-->reset Perspective 窗口-->重置窗口布局 2. windows -> new windows 新窗口 当时手贱了一下,结果…

js的执行机制
javascript的运行机制一直困扰在我,对其的运行机制也是一知半解,在看了https://juejin.im/post/59e85eebf265da430d571f89#heading-10这篇文章后,有种茅塞顿开的感觉,下面是原文内容: 认识javascript javascript是一门单线程语言&…

【Smart_Point】unique_ptr中独占指针使用MakeFrame
1. DFrame使用方法 std::unique_ptr<deptrum::Frame> MakeFrame(deptrum::FrameType frame_type,int width,int height,int bytes_per_pixel,void* data,uint64_t timestamp,int index,float temperature) {std::unique_ptr<deptrum::Frame> frame{std::make_uniq…

最大熵阈值分割
#include <opencv2/imgproc/imgproc.hpp> #include <opencv2/core/core.hpp> #include <opencv2/highgui/highgui.hpp> #include <iostream> using namespace std; using namespace cv; // 计算当前的位置的能量熵 float caculateCurrentE…

php登录注册
php 登录注册 注册代码:register.php <style type"text/css">form{width:300px;background-color:#EEE0E5;margin-left:300px;margin-top:30px;padding:30px;}button{margin-top:20px;} </style> <form method"post"> <la…

【C++】C/C++ 中的单例模式
目录 part 0:单例模式3种经典的实现方式 Meyers Singleton Meyers Singleton版本二 Lazy Singleton Eager Singleton Testing part 1:C之单例模式 动机 实现一[线程不安全版本] 实现二[线程安全,锁的代价过高] 锁机制 实现三[双检…

计算图像波峰点
#include "opencv2/highgui/highgui.hpp" #include "opencv2/imgproc/imgproc.hpp" #include <iostream> #include <stdio.h> #include <iostream> #include <vector> using namespace std; using namespace cv; // 计算图像的波峰…

锁的算法,隔离级别的问题
锁的算法 InnoDB存储引擎有3中行锁的算法设计,分别是: Record Lock:单个行记录上的锁。Gap Lock:间隙锁,锁定一个范围,但不包含记录本身。Next-Key Lock:Gap LockRecord Lock,锁定一…

好程序员分享24个canvas基础知识小结
好程序员分享24个canvas基础知识小结,非常全面详尽,推荐给大家。 现把canvas的知识点总结如下,以便随时查阅。 1、填充矩形 fillRect(x,y,width,height); 2、绘制矩形边框 strokeRect(x,y,width,height); 3、擦除矩形 clearRect(x,y,width,he…

【leetcode】二叉树与经典问题
文章目录笔记leetcode [114. 二叉树展开为链表](https://leetcode-cn.com/problems/flatten-binary-tree-to-linked-list/)解法一: 后序遍历、递归leetcode [226. 翻转二叉树](https://leetcode-cn.com/problems/invert-binary-tree/)思路与算法复杂度分析leetcode [剑指 Offer…

PHP PSR-1 基本代码规范(中文版)
基本代码规范 本篇规范制定了代码基本元素的相关标准,以确保共享的PHP代码间具有较高程度的技术互通性。 关键词 “必须”("MUST")、“一定不可/一定不能”("MUST NOT")、“需要”("REQUIRED")、“将会”("SHALL")、“不会…