为什么需要智能指针
智能指针主要解决以下问题:
1. 内存泄漏:内存手动释放,使用智能指针可以自动释放malloc free; new delete
2. 共享所有权指针的传播和释放,比如多线程使用同一个对象时析构问题
3. 使用普通指针,容易造成堆内存泄露(忘记释放),二次释放,程序发生异常时内存泄露等问题等,使用智能指针能更好的管理堆内存。
c 里面的四个智能指针: auto_ptr,shared_ptr,unique_ptr, weak_ptr 其中后三个是c 11支持,并且第一个已经被c 11弃用。
几个指针的特点:
- unique_ptr独占对象的所有权,由于没有引用计数,因此性能较好。
- shared_ptr共享对象的所有权,但性能略差。
- weak_ptr配合shared_ptr,解决循环引用的问题。
1.1 shared_ptr内存模型
shared_ptr 内部包含两个指针,一个指向对象,另一个指向控制块(control block),控制块中包含一个引用计数(reference count), 一个弱计数(weak count)和其它一些数据。
std::shared_ptr使用引用计数,每一个shared_ptr的拷贝都指向相同的内存。再最后一个shared_ptr析构的时候,内存才会被释放。
shared_ptr共享被管理对象,同一时刻可以有多个shared_ptr拥有对象的所有权,当最后一个shared_ptr对象销毁时,被管理对象自动销毁。
简单来说,shared_ptr实现包含了两部分
一个指向堆上创建的对象的裸指针,raw_ptr。
一个指向内部隐藏的、共享的管理对象。share_count_object。
第一部分没什么好说的,第二部分是需要关注的重点:
use_count,当前这个堆上对象被多少对象引用了,简单来说就是引用计数。
1.2 shared_ptr的基本用法和常用函数
1.2.1构造初始化智能指针shared_ptr
1. 通过构造函数、std::shared_ptr辅助函数和reset方法来初始化shared_ptr,代码如下:
// 智能指针初始化
std::shared_ptr p1(new int(1));
std::shared_ptr p2 = p1;
std::shared_ptr p3;
p3.reset(new int(1));
if(p3) {
cout << "p3 is not null";
}
2. 我们应该优先使用make_shared来构造智能指针,因为他更高效。
auto sp1 = make_shared(100);
//或
shared_ptr sp1 = make_shared(100);
//相当于
shared_ptr sp1(new int(100));
3. 不能将一个原始指针(裸指针)直接赋值给一个智能指针,例如,下面这种方法是错误的:
std::shared_ptr p = new int(1);
初始化
shared_ptr不能通过“直接将原始这种赋值”来初始化,需要通过构造函数和辅助方法来初始化。
s.reset(…):重置shared_ptr;
reset( )不带参数时,若智能指针s是唯一指向该对象的指针,则释放,并置空。若智能指针p不是唯一指向该对象的指针,则引用计数减少1,同时将p置空。
reset( )带参数时,若智能指针s是唯一指向对象的指针,则释放并指向新的对象。若p不是唯一的指针,则只减少引用计数,并指向新的对象。
// 智能指针初始化
std::shared_ptr p1(a);//a use_count=1
std::shared_ptr p2 = p1;//a use_count=2
std::shared_ptr p3;//use_count=0
p3.reset(a);//a use_count=1
// 智能指针有值时reset
std::shared_ptr p1(a);//a use_count=1
std::shared_ptr p2 = p1;//a use_count=2
p2.reset(b);//a use_count=1 , b use_count=1
1.2.2 辅助函数
//返回shared_ptr中保存的裸指针
s.get()
//重置shared_ptr
s.reset(…)
//返回shared_ptr的强引用计数
s.use_count()
//若use_count()为1,返回true,否则返回false
s.unique()
另外智能指针可以通过重载的bool类型操作符来判断
#include
#include
using namespace std;
int main() {
std::shared_ptr p1;
p1.reset(new int(1));
std::shared_ptr p2 = p1;
// 引用计数此时应该是2
cout << "p2.use_count() = " << p2.use_count()<< endl;
p1.reset();
cout << "p1.reset()\n";
// 引用计数此时应该是1
cout << "p2.use_count()= " << p2.use_count() << endl;
if(!p1) {
cout << "p1 is empty\n";
}
if(!p2) {
cout << "p2 is empty\n";
}
p2.reset();
cout << "p2.reset()\n";
cout << "p2.use_count()= " << p2.use_count() << endl;
if(!p2) {
cout << "p2 is empty\n";
}
return 0;
}
当需要获取原始指针时,可以通过get方法来返回原始指针,代码如下所示
std::shared_ptr ptr(new int(1));
int *p = ptr.get(); //不小心 delete p; ---> err
谨慎使用p.get()的返回值,如果你不知道其危险性则永远不要调用get()函数。
p.get()的返回值就相当于一个裸指针的值,不合适的使用这个值,上述陷阱的所有错误都有可能发生,
遵守以下几个约定:
不要保存p.get()的返回值 ,无论是保存为裸指针还是shared_ptr都是错误的
保存为裸指针不知什么时候就会变成空悬指针,保存为shared_ptr则产生了独立指针
不要delete p.get()的返回值 ,会导致对一块内存delete两次的错误
1.2.3指定删除器
如果用shared_ptr管理非new对象或是没有析构函数的类时,应当为其传递合适的删除器。
当p的引用计数为0时,自动调用删除器deleteintptr来释放对象的内存。当我们用shared_ptr管理动态数组时,需要指定删除器,因为shared_ptr的默认删除器不支持数组对象。
#include
#include
using namespace std;
void deleteintptr(int *p) {
cout << "call deleteintptr" << endl;
delete p;
}
int main() {
std::shared_ptr p(new int(1), deleteintptr);
// lambda表达式
std::shared_ptr p2(new int(1), [](int *p) {
cout << "call lambda1 delete p" << endl;
delete p;
});
// 数组删除
std::shared_ptr p3(new int[10], [](int *p) {
cout << "call lambda2 delete p" << endl;
delete[] p;
});
return 0;
}
1.3 使用shared_ptr要注意的问题
1.3.1 不要用一个原始指针初始化多个shared_ptr
int *ptr = new int;
shared_ptr p1(ptr);
shared_ptr p2(ptr); // 逻辑错误,重复释放同一块内存
1.3.2不要在函数实参中创建shared_ptr
对于下面的写法:
function(shared_ptr(new int), g()); //有缺陷
因为c 的函数参数的计算顺序在不同的编译器不同的约定下可能是不一样的,一般是从右到左,但也可能从左到右,所以,可能的过程是先new int,然后调用g(),如果恰好g()发生异常,而shared_ptr还没有创建, 则int内存泄漏了,正确的写法应该是先创建智能指针,代码如下:
shared_ptr p(new int);
function(p, g());
1.3.3通过shared_from_this()返回this指针
如果想让另一个智能指针共享自己,那么要使用shared_from_this()函数,而不应该把this指针作为shared_ptr返回出来。这其实就违法了上面写的“不要用一个原始指针初始化多个shared_ptr”。
不要将this指针作为shared_ptr返回出来,因为this指针本质上是一个裸指针,因此,这样可能会导致重复析构,看下面的例子。
//shared_from_this
#include
#include
using namespace std;
class a {
public:
shared_ptr
死锁问题,如果说两个shared_ptr相互引用,那么这两个指针的引用计数永远不可能下降为0,资源永远不会释放。
3.2 weak_ptr的基本用法
3.2.1构造智能指针weak_ptr
它只可以从一个 shared_ptr 或另一个 weak_ptr 对象构造。
shared_ptr sp(new int(10));
weak_ptr wp(sp);
3.2.2辅助函数
1.通过use_count()方法获取当前观察资源的引用计数,如下所示:
shared_ptr sp(new int(10));
weak_ptr wp(sp);
cout << wp.use_count() << endl; //结果输出1
2.通过expired()方法判断所观察资源是否已经释放,如下所示:
shared_ptr sp(new int(10));
weak_ptr wp(sp);
if (wp.expired())
cout << "weak_ptr无效,资源已释放";
else
cout << "weak_ptr有效";
3.通过lock方法获取监视的shared_ptr,如下所示:
通过lock方法返回shared_ptr,这个shared_ptr共享weak监视的对象,也就是说相等于shared_ptr new=old。
第一次调用f()时,sp还没有释放,use_count=1,所以在lock之后,创建了一个新的shared_ptr共享,那么use_count=2。在第二次调用f()时,sp生命周期已经结束,所以use_count=0,那么lock之后新的shared_ptr也只是指向空,那么必然是无效的,已经释放了。
#include
#include
using namespace std;
std::weak_ptr gw;
void f() {
auto spt = gw.lock();
if (gw.expired()) {
cout << "gw无效,资源已释放";
}
else {
cout << "gw有效, *spt = " << *spt << endl;
}
}
int main() {
{
auto sp = std::make_shared(42);
gw = sp;
f();
}
f();
return 0;
}
3.3 weak_ptr返回this指针
shared_ptr章节中提到不能直接将this指针返回shared_ptr,需要通过派生std::enable_shared_from_this类,并通过其方法shared_from_this来返回指针,原因是std::enable_shared_from_this类中有一个weak_ptr,这个weak_ptr用来观察this智能指针,调用shared_from_this()方法是,会调用内部这个weak_ptr的lock()方法,将所观察的shared_ptr返回,再看前面的范例
//1-1-shared_from_this2
#include
#include
using namespace std;
class a : public std::enable_shared_from_this
线程安全的。
情况2:多线程代码操作的不是同一个shared_ptr的对象
这里指的是管理的数据是同一份,而shared_ptr不是同一个对象。比如多线程回调的lambda的是按值捕获的对象。
std::thread td([sp1]()){....});
另个线程传递的shared_ptr是值传递,而非引用:
void fn(shared_ptr