非常棒的学习博客
在C++中没有垃圾回收机制,必须自己释放分配的内存,否则就会造成内存泄露。
1. shared_ptr
共享智能指针是指多个智能指针可以同时管理同一块有效的内存,共享智能指针
shared_ptr
是一个模板类。智能指针的核心实现技术是引用计数,每使用它一次,内部引用计数加1,每析构一次内部的引用计数减1,减为0时,删除所指向的堆内存。
C++11中提供了三种智能指针,使用这些智能指针时需要引用头文件 <memory>
:
std::shared_ptr
:共享的智能指针std::unique_ptr
:独占的智能指针std::weak_ptr
:弱引用的智能指针,它不共享指针,不能操作资源,是用来监视shared_ptr
的。
共享智能指针对象初始化完毕之后就指向了要管理的那块堆内存,如果想要查看当前有多少个智能指针同时管理着这块内存可以使用共享智能指针提供的一个成员函数 use_count()
初始化
构造函数初始化
// shared_ptr<T> 类模板中,提供了多种实用的构造函数, 语法格式如下:
std::shared_ptr<T> 智能指针名字(创建堆内存);shared_ptr<int> ptr1(new int(123)); // 管理一个 int 型数据对应的堆内存
shared_ptr<char> ptr2(new char[12]); // 管理字符数组对应的堆内存int *p = new int;
shared_ptr<int> p1(p);
shared_ptr<int> p2(p);// error, 编译不会报错, 运行会出错,不要使用一个原始指针初始化多个shared_ptr。
拷贝和移动构造函数初始化
shared_ptr<int> ptr1(new int(520)); // 构造函数初始化
shared_ptr<int> ptr2(ptr1); // 拷贝构造函数
shared_ptr<int> ptr3 = ptr1; // 拷贝赋值函数
shared_ptr<int> ptr4(std::move(ptr1)); // 移动构造函数,转移所有权,此时 ptr1指针置空
shared_ptr<int> ptr5 = std::move(ptr2); // 移动赋值函数,转移所有权,此时 ptr2指针置空
- 使用move不会使内存引用计数增加,作为move参数的 p 指针会被置空
- 使用拷贝构造函数新增的指针会使内存引用计数增加
- 作为参数传递给一个函数以及作为函数的返回值时,内存计数器也会增加
std::make_shared
初始化(最安全的分配和使用动态内存的方法)
make_shared
也定义在头文件 memory
中
// 例子
shared_ptr<int> ptr1 = make_shared<int>(520);
reset 方法初始化
shared_ptr<int> ptr;
ptr.reset(new int(123)); // ptr管理一个整数数据
ptr.reset(); // 内存释放,ptr指针置空
获取原始指针
调用共享智能指针类提供的 get()
方法可以获取原始内存的地址
shared_ptr<int> ptr1 = make_shared<int>(33);
int *ptr2 = ptr1.get();
指定删除器
当智能指针管理的内存对应的引用计数变为0的时候,这块内存就会被智能指针析构掉了。另外,我们在初始化智能指针的时候也可以自己指定删除动作,这个删除操作对应的函数被称之为删除器,这个删除器函数本质是一个回调函数,我们只需要进行实现,其调用是由智能指针完成的。
// 自定义删除器函数,释放int型内存
void deleteIntPtr(int* p){delete p;cout << "int 型内存被释放了...";
}
void test() {shared_ptr<int> ptr(new int(250), deleteIntPtr);shared_ptr<int> ptr(new int(250), [](int* p) {delete p; }); // 使用lambda表达式也可以
}
函数的参数就是智能指针管理的内存的地址,有了这个地址之后函数体内部就可以完成删除操作了。
删除动态数组
shared_ptr<int> ptr(new int[10], default_delete<int[]>()); // c++提供的删除函数
shared_ptr<int> ptr(new int[10], [](int* p) {delete[] p; }); // lambda表达式
可以自己封装一个 make_shared_array
方法来让 shared_ptr
支持数组
template <typename T>
shared_ptr<T> make_shared_array(size_t size)
{// 返回匿名对象return shared_ptr<T>(new T[size], default_delete<T[]>());
}
void test() {shared_ptr<int> ptr1 = make_shared_array<int>(10);cout << ptr1.use_count() << endl;
}
什么是 size_t
?
size_t
是一种无符号的整型数,它的取值没有负数,在数组中也用不到负数,而它的取值范围是整型数的双倍。sizeof操作符的结果类型是 size_t
,它在头文件中 typedef 为 unsigned int
类型。
typedef unsigned int size_t
2. unique_ptr
初始化
std::unique_ptr
是一个独占型的智能指针,它不允许其他的智能指针共享其内部的指针,可以通过它的构造函数初始化一个独占智能指针对象,但是不允许通过赋值将一个 unique_ptr
赋值给另一个unique_ptr
。
// 通过构造函数初始化对象
unique_ptr<int> ptr1(new int(10));
unique_ptr<int> ptr2 = ptr1; // error, 不允许将一个unique_ptr赋值给另一个unique_ptr// 转移所有权,函数返回值赋值
unique_ptr<int> func()
{return unique_ptr<int>(new int(520));
}
void test() {unique_ptr<int> ptr2 = move(ptr1); // 转移所有权,此时 ptr1指针置空unique_ptr<int> ptr3 = func(); // 通过函数返回给其他的 unique_ptr
}// reset
// 使用reset方法可以让unique_ptr解除对原始内存的管理,也可以用来初始化一个独占的智能指针。
unique_ptr<int> ptr1;
ptr1.reset(new int(10));
ptr1.reset(); // ptr1指针置空
get()方法
与 shared_ptr
相同
删除器
unique_ptr
指定删除器和 shared_ptr
指定删除器是有区别的,unique_ptr
指定删除器的时候需要确定删除器的类型,所以不能像 shared_ptr
那样直接指定删除器。
实例:
shared_ptr<int> ptr1(new int(10), [](int*p) {delete p; }); // ok
unique_ptr<int> ptr1(new int(10), [](int*p) {delete p; }); // error// 使用函数指针
using func_ptr = void(*)(int*);
unique_ptr<int, func_ptr> ptr1(new int(10), [](int*p) {delete p; });
在上面的代码中第7行,func_ptr
的类型和 lambda表达式
的类型是一致的。
在lambda表达式没有捕获任何变量的情况下是正确的,但是如果捕获了变量,编译时则会报错:
using func_ptr = void(*)(int*);
unique_ptr<int, func_ptr> ptr1(new int(10), [&](int*p) {delete p; }); // error
上面的代码中错误原因是这样的,在lambda表达式没有捕获任何外部变量时,可以直接转换为函数指针,一旦捕获了就无法转换了,如果想要让编译器成功通过编译,那么需要使用可调用对象包装器来处理声明的函数指针:
#include <functional> // function 的头文件
unique_ptr<int, function<void(int*)>> ptr1(new int(10), [&](int*p) {delete p; });
weak_ptr
弱引用智能指针 std::weak_ptr
可以看做是 shared_ptr
的助手,它不管理 shared_ptr
内部的指针。std::weak_ptr
没有重载操作符 *
和 ->
,因为它不共享指针,不能操作资源,所以它的构造不会增加引用计数,析构也不会减少引用计数,它的主要作用就是作为一个旁观者监视 shared_ptr
中管理的资源是否存在。
shared_ptr<int> sp(new int);weak_ptr<int> wp1; // 构造了一个空weak_ptr对象
weak_ptr<int> wp2(wp1); // 拷贝构造
weak_ptr<int> wp3(sp); // 通过一个shared_ptr对象构造了一个可用的weak_ptr实例对象
weak_ptr<int> wp4;
wp4 = sp; // wp4监视sp(隐式类型转换)
weak_ptr<int> wp5;
wp5 = wp3; // 赋值
use_count 方法
通过调用 std::weak_ptr
类提供的use_count()
方法可以获得当前所观测资源的引用计数。
上述代码中,wp3
、wp4
、wp5
监测的资源是同一个
expired 方法
通过调用std::weak_ptr
类提供的expired()
方法来判断所观测的资源(shared_ptr指向的内存)是否已经被释放。若 w.use_count()
为 0,返回 true
,否则返回 false
。
shared_ptr
指针可以通过 reset()
释放资源
lock 方法
通过调用std::weak_ptr
类提供的 lock()
方法来获取所监测的shared_ptr
对象。
reset 方法
通过调用std::weak_ptr
类提供的reset()
方法来放弃监视对象,使其不监测任何资源。
因为不再监控任何资源,因此
返回管理this的shared_ptr
C++11中为我们提供了一个模板类叫做std::enable_shared_from_this<T>
,这个类中有一个方法叫做shared_from_this()
,通过这个方法可以返回一个共享智能指针。在函数的内部就是使用weak_ptr
来监测this
对象,并通过调用weak_ptr
的lock()
方法返回一个shared_ptr
对象。
class Test : public enable_shared_from_this<Test>
{
public:shared_ptr<Test> getSharedPtr(){return shared_from_this();}// shared_ptr<Test> getSharedPtr() {return shared_ptr<Test>(this);}// 直接返回this指针会导致通过getSharedPtr()初始化的shared_ptr的引用计数不会增加,最后被对象被析构两次的错误~Test(){cout << "class Test is disstruct ..." << endl;}
};
int main()
{shared_ptr<Test> sp1(new Test); // 必须先对 sp1初始化,保证weak_ptr具有检测对象,才能调用下边的getSharedPtr()cout << "use_count: " << sp1.use_count() << endl;shared_ptr<Test> sp2 = sp1->getSharedPtr();cout << "use_count: " << sp1.use_count() << endl;return 0;
}
解决循环引用问题
ap 和 aptr 是指向类TA的共享指针,bp 和 bptr 是指向类TB的贡献指针
红色箭头表示共享指针指向申请的空间,蓝色箭头是对象里的智能指针指向内存空间
共享智能指针ap、bp对TA、TB实例对象的引用计数变为2,在共享智能指针离开作用域之后引用计数只能减为1,这种情况下不会去删除智能指针管理的内存,导致类TA、TB的实例对象不能被析构,最终造成内存泄露。
通过使用weak_ptr
可以解决这个问题,只要将类TA或者TB的任意一个成员改为weak_ptr
,修改之后的代码如下:
struct TA;
struct TB; // 声明类struct TA{weak_ptr<TB> bptr; // 将其中一个类里的shared_ptr改成weak_ptr即可解决~TA(){cout << "class TA is disstruct ..." << endl;}
};
struct TB{shared_ptr<TA> aptr;~TB(){cout << "class TB is disstruct ..." << endl;}
};
void testPtr(){shared_ptr<TA> ap(new TA);shared_ptr<TB> bp(new TB);cout << "TA object use_count: " << ap.use_count() << endl;cout << "TB object use_count: " << bp.use_count() << endl;ap->bptr = bp;bp->aptr = ap;cout << "TA object use_count: " << ap.use_count() << endl;cout << "TB object use_count: " << bp.use_count() << endl;
}
上面程序中,在对类TA成员赋值时
ap->bptr = bp
;由于bptr
是weak_ptr
类型,这个赋值操作并不会增加引用计数,所以bp
的引用计数仍然为1,在离开作用域之后bp
的引用计数减为0,类TB的实例对象被析构。
在类TB的实例对象被析构的时候,内部的
aptr
也被析构,其对TA对象的管理解除,内存的引用计数减为1,当共享智能指针ap
离开作用域之后,对TA对象的管理也解除了,内存的引用计数减为0,类TA的实例对象被析构。