目录
1.2.1 auto_ptr(C++98的方案,C++11已经抛弃)采用所有权模式。
C++里面的四个智能指针: auto_ptr, unique_ptr,shared_ptr, weak_ptr 其中后三个是C++11支持,并且第一个已经被C++11弃用。
智能指针主要用于管理在堆上分配的内存,它将普通的指针封装为一个栈对象。当栈对象的生存周期结束后,会在析构函数中释放掉申请的内存,从而防止内存泄漏。C++ 11中最常用的智能指针类型为shared_ptr,它采用引用计数的方法,记录当前内存资源被多少个智能指针引用。该引用计数的内存在堆上分配。当新增一个时引用计数加1,当过期时引用计数减一。只有引用计数为0时,智能指针才会自动释放引用的内存资源。对shared_ptr进行初始化时不能将一个普通指针直接赋值给智能指针,因为一个是指针,一个是类。可以通过make_shared函数或者通过构造函数传入普通指针。并可以通过get函数获得普通指针。
智能指针的作用是管理一个指针,因为存在以下这种情况:申请的空间在函数结束时忘记释放,造成内存泄漏。使用智能指针可以很大程度上的避免这个问题,因为智能指针是一个类,当超出了类的实例对象的作用域时,会自动调用对象的析构函数,析构函数会自动释放资源。所以智能指针的作用原理就是在函数结束时自动释放内存空间,不需要手动释放内存空间。
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 一样的效果。
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
{
//省略其他代码...
//拷贝构造函数和赋值运算符被标记为delete
unique_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类型的堆对象
//形式1
std::unique_ptr<int[]> sp1(new int[10]);
//形式2
std::unique_ptr<int[]> sp2;
sp2.reset(new int[10]);
//形式3
std::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;
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()
{
{
//初始化方式1
std::shared_ptr<A> sp1(new A());
std::cout << "sp1 use count: " << sp1.use_count() << std::endl;
//初始化方式2
std::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; //2
cout<<ps2.use_count()<<endl; //2
cout << ps1.unique()<<endl; //0
string *s3 = new string("s3");
shared_ptr<string> ps3(s3);
cout << (ps1.get()) << endl; //033AEB48
cout << ps3.get() << endl; //033B2C50
swap(ps1, ps3); //交换所拥有的对象
cout << (ps1.get())<<endl; //033B2C50
cout << ps3.get() << endl; //033AEB48
cout << ps1.use_count()<<endl; //1
cout << ps2.use_count() << endl; //2
ps2 = ps1;
cout << ps1.use_count()<<endl; //2
cout << ps2.use_count() << endl; //2
ps1.reset(); //放弃ps1的拥有权,引用计数的减少
cout << ps1.use_count()<<endl; //0
cout << ps2.use_count()<<endl; //1
}
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; //1
cout << pa.use_count() << endl; //1
pb->pa_ = pa;
pa->pb_ = pb;
cout << pb.use_count() << endl; //2
cout << 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
内部对象的计数.
share_ptr
和weak_ptr
的核心实现weak_ptr
的作为弱引用指针,其实现依赖于counter的计数器类和share_ptr
的赋值,构造,所以先把counter和share_ptr
简单实现
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对象。
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的内存。
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 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