目录
前言
本文为之前工作中写了一些技术分享文档之一,主要是谈谈智能指针的使用,事实上不是非常有必要,基本上都是使用智能指针而不会使用裸指针(C++11之前的版本没办法)。本文引用了不少博文,具体引用在文末。
出现的原因
-
避免了new之后忘记delete的问题,尤其在复杂的程序中,在正确的地方释放内容是极其困难的。
-
若果有两个指针指向同一块内存,在一个指针将这块内存释放掉后,另一个指针就成了空悬指针,解引用空悬指针会造成不可知的错误
智能指针与裸指针的最大不同就是智能指针管理它所指内存的生命周期,当一块内存不再被需要后,智能指针就会自动的释放它。最早出现与boost库中,后来在C++11中添加进来。
一个典型的例子:
void remodel(std::string & str)
{
std::string * ps = new std::string(str);
...
if (weird_thing())
throw exception();
str = *ps;
delete ps;
return;
}
智能指针
智能指针分为 shared_ptr(共享指针) 和 unique_ptr(独占指针)
-
共享指针(shared_ptr) 是指由几个关联的共享指针共享一块内存,用use_count(引用计数)来表示有几个共享指针使用这块内存,只有当所有共享指针都不指向这块内存后,引用计数变0,这块内存才会被自动释放。
-
独占指针(unique_ptr) 是指由一个指针独占一块内存,当独占指针不再指向这块内存时,自动将其释放。注意独占指针有个方法(u.release())可以放弃对内存的控制权,并返回指向这块内存的指针(供其他指针使用),但并不释放内存。
-
弱指针(weak_ptr) 同时标准库还定义了弱指针(weak_ptr),之所以称作弱指针是因为它并不控制所指内存的生存期,这一点与裸指针十分相似,但弱指针上定义了w.use_count、w.lock()等方法让我们在使用弱指针之前就知道所指内存有没有被释放,只有在内存没有被释放的时候我们才使用它,避免了裸指针的空悬指针错误。(更倾向于辅助类)
共享指针和独占指针一个最明显的不同其实就是,共享指针多了一个引用计数,因而可以支持多个指针指向同一个对象
实现功能
基本特性
-
模板类
-
使用上和普通指针没有区别,因为重载了 operator* 和 operator-> , 所以可以像通常的指针一样使用它。(weak_ptr没有)
-
shared_ptr 主要的功能是,管理动态创建的对象的销毁。它的基本原理就是记录对象被引用的次数,当引用次数为 0 的时候,也就是最后一个指向某对象的共享指针析构的时候,共享指针的析构函数就把指向的内存区域释放掉。
-
共享指针允许多个指针引用同一个对象,而unique_ptr只能是一个。
常用的函数
以shared_ptr为例
get() // 返回保存的指针
use_count() // 获取引用计数个数
unique() // 返回是否独占,即判断 use_count 是否为 1
swap() // 交换两个 shared_ptr 拥有的对象
reset() // 放弃内部对象的拥有权,即原对象的引用计数减一
实现原理
1、指向对象的指针。(可以用get()返回) 2、用于控制引用计数数据的指针。
实现: shared_ptr模板类有一个shared_count类型的成员_M_refcount来处理引用计数的问题。shared_count也是一个模板类,它的内部有一个指向Sp_counted_base_impl类型的指针M_pi。所有引用同一个对象的shared_ptr都共用一个M_pi指针。
当一个shared_ptr拷贝复制时, M_pi指针调用M_add_ref_copy()函数将引用计数+1。 当shared_ptr析构时,M_pi指针调用M_release()函数将引用计数-1。 _M_release()函数中会判断引用计数是否为0. 如果引用计数为0, 则将shared_ptr引用的对象内存释放掉。
__shared_count(const __shared_count& __r)
: _M_pi(__r._M_pi) // nothrow
{
if (_M_pi != 0)
_M_pi->_M_add_ref_copy();
}
如何选择智能指针
(1)如果程序要使用多个指向同一个对象的指针,应选择shared_ptr。这样的情况包括:
-
有一个指针数组,并使用一些辅助指针来标示特定的元素,如最大的元素和最小的元素;
-
两个对象包含都指向第三个对象的指针;
-
STL容器包含指针。很多STL算法都支持复制和赋值操作,这些操作可用于shared_ptr,但不能用于unique_ptr(编译器发出warning)和auto_ptr(行为不确定)。如果你的编译器没有提供shared_ptr,可使用Boost库提供的shared_ptr。
(2)如果程序不需要多个指向同一个对象的指针,则可使用unique_ptr。
举个例子:
好比教室里的电灯,大家都在使用这个电灯,但没有那个人来专门负责这个电灯的开关。往往是教室里一个人都没有,电灯却明晃晃地亮着。
在这种场景下,我们就可以使用shared_ptr来管理这个电灯,通过shared_ptr,谁都可以使用这个电灯,但谁最后使用电灯谁就负责最后关灯。
new和maked_share的区别
// new方式
A* a = new A;
std::shared_ptr<A> pa(a);
// make_shared方式
std::shared_ptr<A> pa1 = std::make_shared<A>(1);
区别:
new方式:先在堆上分配一块内存,然后在堆上再建一个智能指针控制块,内存不一定是连续的,会造成内存碎片化
make_shared方式:直接在堆上新建一块足够大的内存,其中包含两部分,上面是内存(用来使用),下面是控制块(包含引用计数),然后用A的构造函数去初始化分配的内存(分配一块内存的步骤:先分配内存,再进分配的内存调用构造函数进行构造,构造完毕才能使用)
作用
(1) 智能指针主要的用途就是方便资源的管理,自动释放没有指针引用的资源。
(2) 使用引用计数来标识是否有多余指针指向该资源。(注意,shared_ptr本身指针会占1个引用)
(3) 在赋值操作中, 原来资源的引用计数会减一,新指向的资源引用计数会加一。
std::shared_ptr<Test> p1(new Test);
std::shared_ptr<Test> p2(new Test);
p1 = p2;
(4) 引用计数加一/减一操作是原子性的,所以线程安全的。
(5) make_shared要优于使用new,make_shared可以一次将需要内存分配好。
std::shared_ptr<Test> p = std::make_shared<Test>();
std::shared_ptr<Test> p(new Test);
(6) std::shared_ptr的大小是原始指针的两倍,因为它的内部有一个原始指针指向资源,同时有个指针指向引用计数。
(7) 引用计数是分配在动态分配的,std::shared_ptr支持拷贝,新的指针获可以获取前引用计数个数。
经典陷阱——循环引用
class A
{
public:
shared_ptr<A> pre;
shared_ptr<A> next;
~A()
{
cout << "Destructed." << endl;
}
};
int main()
{
//一个作用域
{
shared_ptr<A> sp_a1(new A),sp_a2(new A);
sp_a1->next = sp_a2;
sp_a2->pre = sp_a1;
}
//我们期望在离开这个作用域之后,sp_a1和sp_a2会释放它们控制的内存
system("pause");
return 0;
}
//在离开作用域后,程序没有没有输出析构函数中的Destructed
//原因是这两块内存上的共享指针构成了循环引用

由于先构造的后释放,后构造的先释放可知,先释放的是sp2,那么因为它的引用计数为2,减去1之后就成为了1,不能释放空间,因为还有其他的对象在管理这块空间。但是sp2这个变量已经被销毁,因为它是栈上的变量,但是sp2管理的堆上的空间并没有释放。 接下来释放sp1,同样,先检查引用计数,由于sp1的引用计数也是2,所以减1后成为1,也不会释放sp1管理的动态空间。
通俗点讲:就是sp2要释放,那么必须等p1释放了,而sp1要释放,必须等sp2释放,所以,最终,它们两个都没有释放空间。
这几个指针间构成了一个死锁,这两块内存直到程序结束前都不会被释放
弱指针怎么解决这个问题的?
class A
{
public:
//弱指针不具有对象的生命周期控制权,避免形成死锁
weak_ptr<A> pre;
weak_ptr<A> next;
~A()
{
cout << "Destructed." << endl;
}
};
因为weak_ptr不会增加引用计数,使得引用形不成环,最后就可以正常的释放内部的对象,不会造成内存泄漏
总结
总的来说,智能指针并没有杜绝头疼的内存泄露问题,但合理使用,还是能降低风险。
引用:
本文详细介绍了智能指针的起源、类型(shared_ptr和unique_ptr)、功能特性,重点讲解了如何选择使用,以及new和make_shared的区别。讨论了循环引用的陷阱及弱指针的解决方案,强调了智能指针在资源管理和避免内存泄露中的作用。
2万+

被折叠的 条评论
为什么被折叠?



