C++11 谈谈shared_ptr(细节)
个人用!十分主观!仅供参考!
shared_ptr是C++11中加入的一种智能指针(其实并不够智能),其作用就是帮助我们管理在堆中开辟的空间,避免野指针等众多内存管理不当造成的问题。
重点:智能指针会自动的给我们释放开辟的内存空间
实际上,每种智能指针都是以类模板的方式实现的,shared_ptr
shared_ptr使用了引用计数机制,也就是其类内维护了一个计数count。其之所以叫做shared,多个shared_ptr可以共同使用同一块堆内存,一旦多了一个新的shared_ptr使用了这块内存,这个计数count就会+1。
初始化
//显式
shared_ptr<int> p1;//默认初始化为nullptr,默认构造
shared_ptr<int> p2(new int(10));//使用指针初始化,含参构造
shared_ptr<int> p3(p2);//拷贝构造
shared_ptr<int> p4(move(p3));//移动构造
不够智能的智能指针
注意,不能用同一个普通指针初始化超过一个智能指针。
int* p = new int(10);
shared_ptr<int> p1(p);
shared_ptr<int> p2(p);
上面的语句编译不会出错,但运行时会崩掉,因为程序结束时,智能指针会自动释放p1,p2的内存。在此p1,p2的计数都是1,故p这块内存被释放了两次,释放第二次的时候程序崩掉了。
同理,还有下面这种情况:
int* p = new int(10);
shared_ptr<int> p1(p);
delete p;
同样的原因,释放已经释放的内存,程序崩掉了。
因此,智能指针的使用要和传统的指针用法相区别开。
shared_ptr模板类提供的成员方法
参考文章C++11 shared_ptr智能指针(超级详细) (biancheng.net)
成员方法名 | 功 能 |
---|---|
operator=() | 重载赋值号,使得同一类型的 shared_ptr 智能指针可以相互赋值。 |
operator*() | 重载 * 号,获取当前 shared_ptr 智能指针对象指向的数据。 |
operator->() | 重载 -> 号,当智能指针指向的数据类型为自定义的结构体时,通过 -> 运算符可以获取其内部的指定成员。 |
swap() | 交换 2 个相同类型 shared_ptr 智能指针的内容。 |
reset() | 当函数没有实参时,该函数会使当前 shared_ptr 所指堆内存的引用计数减 1,同时将当前对象重置为一个空指针;当为函数传递一个新申请的堆内存时,则调用该函数的 shared_ptr 对象会获得该存储空间的所有权,并且引用计数的初始值为 1。 |
get() | 获得 shared_ptr 对象内部包含的普通指针。 |
use_count() | 返回同当前 shared_ptr 对象(包括它)指向相同的所有 shared_ptr 对象的数量。 |
unique() | 判断当前 shared_ptr 对象指向的堆内存,是否不再有其它 shared_ptr 对象再指向它。 |
operator bool() | 判断当前 shared_ptr 对象是否为空智能指针,如果是空指针,返回 false;反之,返回 true。 |
除此之外,C++11 标准还支持同一类型的 shared_ptr 对象,或者 shared_ptr 和 nullptr 之间,进行 ==,!=,<,<=,>,>= 运算。
主要用到的是use_count()和reset()。最后一个 operator bool(),这是类型转换。
自定义释放规则
对于在类中开辟堆内存的情况:
- 可以在类中析构中释放(因为智能指针计数为0时实际就是调用的类的析构);
- 可以自定义释放规则;
- 可以将类中的指针也定义为智能指针;
先看一段代码:
//定义了一个类,类中开辟了堆内存,用p指向
class tmpA
{
public:
tmpA():p(new int(10)){}
int* p;
};
void test()
{
shared_ptr<tmpA> p1(new tmpA);//创建一个指向tmpA类型的智能指针
int* p2 = p1->p;//保存p1指向的对象中的p的值
cout << "对象释放前: *p2 = " << p2 << endl;
p1.reset();//引用计数-1 =0 , 释放p1指向的空间
cout << "对象释放后:p1 = " << p1 << endl;//看是否释放成功(是否为0)
cout << "对象释放后:*p2 = " << *p2 << endl;//看对象中开辟的堆空间是否释放
}
结果:
这显然没有释放类中的堆内存
- 类析构中释放
//定义了一下析构函数
class tmpA
{
public:
tmpA():p(new int(10)){}
~tmpA() {
if (p)
{
delete p;
p = nullptr;
}
}
int* p;
};
结果:
释放了
- 自定义释放规则
void test()
{
shared_ptr<tmpA> p1(new tmpA,
[](tmpA* cptr) {
if (cptr->p) {
delete cptr->p;
cptr->p = nullptr;
}
}
);//创建一个指向tmpA类型的智能指针,并自定义释放规则(这里用了lambda表达式)
int* p2 = p1->p;//保存p1指向的对象中的p的值
cout << "对象释放前: *p2 = " << *p2 << endl;
p1.reset();//引用计数-1 =0 , 释放p1指向的空间
cout << "对象释放后:p1 = " << p1 << endl;//看是否释放成功(是否为0)
cout << "对象释放后:*p2 = " << *p2 << endl;//看对象中开辟的堆空间是否释放
delete p2;
p2 = nullptr;
}
结果:
释放了
- 类内指向堆内存的指针定义为智能指针
class tmpA
{
public:
tmpA():p(new int(10)){}
shared_ptr<int> p;
};
void test()
{
shared_ptr<tmpA> p1(new tmpA);//创建一个指向tmpA类型的智能指针
int* p2 = p1->p.get();//保存p1指向的对象中的p的值(从shared<int>转到int* 要用get()函数)
cout << "对象释放前: *p2 = " << *p2 << endl;
p1.reset();//引用计数-1 =0 , 释放p1指向的空间
cout << "对象释放后:p1 = " << p1 << endl;//看是否释放成功(是否为0)
cout << "对象释放后:*p2 = " << *p2 << endl;//看对象中开辟的堆空间是否释放
delete p2;
p2 = nullptr;
}
结果:
释放了。
其实也可以用对象中开辟的堆内存初始化一个智能指针,也就是把int p2 改成 shared p2*
这其实也是第三种方法
综上
智能指针的使用要理解其中原理,不然很容易造成过度释放。