智能指针
注意智能指针管理的是动态分配的内存,即堆区的数据,栈上的会有问题
#include <memory> // 智能指针的公用头文件
shared_ptr 共享
共享指针(是指可以多个
shared_ptr
指向同一块内存,进行管理)
好处:
- 方便调用,重要的是防止内存泄漏
- 因为我们自己定义指针,
new
出空间,就得手动释放,但是智能指针是一个模板类,封装好了的,当不需要的时候,会在智能指针生命周期结束后自动释放内存 - 比如定义在函数里,函数结束,它就结束,自动释放内存,还比如它不再指向内存空间了,就自动释放内存,指向空
初始化
默认构造函数
// shared_ptr<T> 类模板中,提供了多种实用的构造函数, 语法格式如下:
std::shared_ptr<T> 智能指针名字(创建堆内存);
shared_ptr<int> ptr1(new int(520));
std::make_shared
template< class T, class... Args >
shared_ptr<T> make_shared( Args&&... args );
shared_ptr<int> ptr1 = make_shared<int>(520);
class Person{
public:
Person(string name, int age):m_name(name),m_age(age){}
string m_name;
int m_age;
};
shared_ptr<Person> ptr2 = make_shared<Person>("zhangsan", 1);
T
:模板参数的数据类型Args&&... args
:要初始化的数据,如果是通过make_shared
创建对象,需按照构造函数的参数列表指定
reset
void reset() noexcept;
template<class T>
void reset(T* ptr);
template<class T, class Deleter>
void reset(T* ptr, Deleter d);
template<class T, class Deleter, class Alloc>
void reset(T* ptr, Deleter d, Alloc alloc);
ptr5.reset(new int(250));
ptr
:指向要取得所有权的对象的指针d
:指定删除器,当不再使用这个共享指针的时候,就调用该删除器销毁对象alloc
:内部存储所用的分配器
==综合示例==
class Test {
public:
Test() {}
Test(int x) {
cout << "construct Test, x = " << x << endl;
}
Test(string str) {
cout << "construct Test, str = " << str << endl;
}
~Test() {}
};
int main()
{
shared_ptr<int> ptr1(new int(520));
//调用拷贝构造函数
shared_ptr<int> ptr2(ptr1);
shared_ptr<int> ptr3 = ptr1;
//调用移动构造函数
shared_ptr<int> ptr4(std::move(ptr1));
shared_ptr<int> ptr5 = std::move(ptr2);
// make_shared 初始化
shared_ptr<int> ptr6 = make_shared<int>(520);
shared_ptr<Test> ptr7 = make_shared<Test>("zhangsan");
shared_ptr<Test> ptr8 = make_shared<Test>(520);
// reset 初始化
ptr4.reset(); // 这样之后这个指针释放空间,不指向任何
ptr5.reset(new int(250));
ptr8.reset(new int(250)); // 错误,类型不一致,一个为 Test 类,一个为 int
}
内部函数
// 管理当前对象的 shared_ptr 实例数量,或若无被管理对象则为 0。
long use_count() const noexcept;
ptr.use_count; // 表示这个内存被多少个 shared_ptr 指针管理
// 获取原始指针(最开始的指针,指向空间的),也就是在智能指针管理之前的管理内存的指针
T* get() const noexcept;
shared_ptr<int> ptr1(new int(520));
int *p = ptr1.get();
Test *t = ptr.get();
t->setvalue(100); // 假设 Test 类具有这个 setvalue
- 上面
reset
和内部函数方法使用.
,而这里使用->
本质是内部重载了这些符号,这是个类模板,这些方法是内部成员函数,使用.
- 而指针的使用是
->
,是为了方便我们把他就当成指针运算,方便记忆和使用,所以内部重载了->
,使其和指针一样
注意点
int *a = new int(10);
shared_ptr<int>ptr(a);
*ptr = 100;
cout << *a << endl; // 输出 100
cout << ptr.use_count(); // 输出 1
- 说明这个只是统计智能指针的数量,使用智能指针修改数据,实际也可改变,说明指针确实在管理内存
指定删除器
智能指针不指定删除器,会自动调用内部的删除器,将其内存释放,但也可指定
shared_ptr<int> ptr(new int(250), [](int *t){
delete t;
});
// 这样直接结束后,调用析构函数,就会调用这个 lambda 构成的函数
但是数组不具有默认的删除器,因此需指定(C++11 不会,C++11 以后可以了)
shared_ptr<int> ptr(new int[10], [](int* p) {
delete []p;
});
- 同时,编译器也提供了删除器的模板函数
std::default_delete<T>();
shared_ptr<int> ptr(new int[10], default_delete<int []>());
unique_ptr 独占
只允许一个智能指针管理内存,但是不包括普通指针不能管理
初始化
unique_ptr<int> func() {
return unique_ptr<int>(new int(520));
}
int main()
{
// 通过构造函数初始化
unique_ptr<int> ptr1(new int(10));
// 通过转移所有权的方式初始化
unique_ptr<int> ptr2 = std::move(ptr1);
// 即将释放的独占智能指针
unique_ptr<int> ptr3 = func();
// reset
ptr1.reset();
ptr2.reset(new int(250));
}
// 同样支持 get 方法
注意
int main()
{
int *a = new int(10);
unique_ptr<int>ptr(a); // 这个定义是正确的
*ptr = 100;
cout << *a << endl; // 输出 100
}
// 说明普通指针也能指向,独占智能指针也在管理内存
指定删除器
语法和共享的类似
展示区别
shared_ptr<int> ptr1(new int(10), [](int*p) {delete p; }); // 正确
unique_ptr<int> ptr1(new int(10), [](int*p) {delete p; }); // 错误
int main()
{
// 这样才正确,需要指定删除器函数类型,这里就是函数指针类型
using func_ptr = void(*)(int*);
unique_ptr<int, func_ptr> ptr1(new int(10), [](int*p) {delete p; });
}
- 如果上述的
lambda
表达式中[]
里面传参数,就又不对了,捕捉了外部变量,此时lambda
就不能看作是函数指针了,此时是一个仿函数 lambda 作为仿函数类型
我们不方便指定其类型- 使用包装器解决或者不使用
lambda
表达式,就使用一个函数,然后就能指定类型
void deleteInt(int* p) {
delete p;
}
int main()
{
// 错误演示
using func_ptr = void(*)(int*);
unique_ptr<int, func_ptr> ptr1(new int(10), [=](int*p) {delete p; });
// 正确,使用包装器解决
unique_ptr<int, function<void(int*)>> ptr1(new int(10), [&](int*p) {delete p; });
// 正确,使用定义的函数解决
using func_ptr = void(*)(int*);
unique_ptr<int, func_ptr> ptr1(new int(10), deleteInt);
}
// 对捕捉变量后的 lambda 表达式不是函数指针疑惑的,详情见 lambda 表达式章节
// 独占的 C++11 就支持对数组调用默认删除器
// 而共享的 C++11 后支持
unique_ptr<int[]> ptr(new int [3]);
weak_ptr 弱引用
它不共享指针,不能操作资源,是用来监视
shared_ptr
的
- 弱引用智能指针
std::weak_ptr
可以看做是shared_ptr
的助手,它不管理shared_ptr
内部的指针 std::weak_ptr
没有重载操作符*
和->
,因为它不共享指针,不能操作资源,所以它的构造不会增加引用计数(指向当前内存空间的智能指针数量),析构也不会减少引用计数,它的主要作用就是作为一个旁观者监视shared_ptr
中管理的资源是否存在
初始化
语法:
// 默认构造函数
constexpr weak_ptr() noexcept;
// 拷贝构造
weak_ptr (const weak_ptr& x) noexcept;
template <class T> weak_ptr (const weak_ptr<T>& x) noexcept;
// 通过shared_ptr对象构造
template <class T> weak_ptr (const shared_ptr<T>& x) noexcept;
shared_ptr<int> sp(new int (10));
weak_ptr<int> wp1 = new int (11); // 错误,需要在 shared_ptr 基础上创建,监视作用
weak_ptr<int> wp2(sp);
weak_ptr<int> wp3(wp2);
weak_ptr<int> wp4 = sp;
weak_ptr<int> wp5 = wp3;
内部函数
use_count()
// 函数返回所监测的资源的引用计数
long int use_count() const noexcept;
weak_ptr
只是监测资源,并不管理资源,所以不会导致计数加一
expired()
// 返回 true 表示 shared_ptr 指向的资源已经被释放, 返回 false 表示资源没有被释放
bool expired() const noexcept;
示例
shared_ptr<int> shared(new int(10));
weak_ptr<int> weak(shared);
cout << "1. weak " << (weak.expired() ? "is" : "is not") << " expired" << endl;
shared.reset();
cout << "2. weak " << (weak.expired() ? "is" : "is not") << " expired" << endl;
执行结果:
1. weak is not expired
2. weak is expired
lock()
// 获取所监测资源的 shared_ptr 对象
shared_ptr<element_type> lock() const noexcept;
reset()
// 清空对象,使其不检测任何资源
void reset() noexcept;
解决 shared_ptr 遗留的问题
不能使用一个原始地址初始化多个共享智能指针
struct Test {
Test(){};
~Test() {
cout << "class Test is disstruct ..." << endl;
}
};
int main()
{
Test *t = new Test;
shared_ptr<Test> ptr1(t);
cout << "use_count: " << ptr1.use_count() << endl;
shared_ptr<Test> ptr2(t);
cout << "use_count: " << ptr2.use_count() << endl;
return 0;
}
执行结果:
use_count: 1
use_count: 1
析构了
析构了
- 调用了两次析构,一个是
ptr1
,一个是ptr2
- 这就有问题,一块空间释放了两次
- 这里的错误是使用了原始地址
t
去初始化了多个共享智能指针,正确写法是shared_ptr<Test> ptr2(ptr1);
- 还有人可能疑惑第一行
Test *t = new Test
为啥没有析构函数调用
Test *t = new Test;
delete t;
// 因为是创建在堆区,需要手动释放,写 delete 就会调用析构函数
Test t;
// 这样写,生命周期结束后就会自动调用析构函数
函数不能返回管理了 this 的共享智能指针
需求是通过成员函数返回管理当前对象的共享智能指针
- 错误实现
struct Test {
shared_ptr<Test> getSharedPtr() {
return shared_ptr<Test>(this);
}
~Test() {
cout << "析构函数调用" << endl;
}
};
int main()
{
shared_ptr<Test> ptr1(new Test);
cout << "use_count: " << ptr1.use_count() << endl;
shared_ptr<Test> ptr2 = ptr1->getSharedPtr();
cout << "use_count: " << ptr2.use_count() << endl;
return 0;
}
use_count: 1
use_count: 1
析构函数调用
析构函数调用
- 这本质和上面那个 错误一致,都是使用了原始地址初始化了两次智能指针,导致出错
- 因为返回
shared_ptr
,在成员函数里使用的是this
初始化shared_ptr
,再赋值给ptr2
,而this
就是ptr1
初始化的new Test
,所以就导致new Test
的地址分配给ptr1
和ptr2
- 就和第一个一样的问题了
解决方案就是通过 weak_ptr
来解决,通过 wek_ptr
返回管理 this
资源的共享智能指针对象 shared_ptr
std::enable_shared_from_this<T> // 模板类
shared_from_this() // 所调用的成员方法
- 它的本质实现是使用了
weak_ptr
里的lock()
方法实现,返回shared_ptr
struct Test : public enable_shared_from_this<Test> {
shared_ptr<Test> getSharedPtr() {
return shared_from_this();
}
~Test() {
cout << "析构函数调用" << endl;
}
};
int main()
{
shared_ptr<Test> ptr1(new Test);
cout << "use_count: " << ptr1.use_count() << endl;
shared_ptr<Test> ptr2 = ptr1->getSharedPtr();
cout << "use_count: " << ptr2.use_count() << endl;
return 0;
}
执行结果:
use_count: 1
use_count: 2
析构函数调用
- 在调用
enable_shared_from_this
类的shared_from_this ()
方法之前,必须要先初始化函数内部weak_ptr
对象,否则该函数无法返回一个有效的shared_ptr
对象
weak_ptr
是如何隐式创建的?
- 当你使用
shared_ptr
创建对象并且该对象的类继承了enable_shared_from_this
,在对象创建时,enable_shared_from_this
内部会生成一个指向该对象的weak_ptr
。这个weak_ptr
用来跟踪当前对象,确保shared_from_this()
返回的shared_ptr
是安全的。
解决循环引用问题
struct TA;
struct TB;
struct TA {
shared_ptr<TB> bptr;
~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 << "TA1 use_count: " << ap.use_count() << endl;
cout << "TB1 use_count: " << bp.use_count() << endl;
ap->bptr = bp;
bp->aptr = ap;
cout << "TA2 use_count: " << ap.use_count() << endl;
cout << "TB2 use_count: " << bp.use_count() << endl;
}
int main() {
testPtr();
}
TA1 use_count: 1
TB1 use_count: 1
TA2 use_count: 2
TB2 use_count: 2
- 会发现没有析构函数的调用,但正确的应该有两个
- 这个原因是循环了,最终的计数都为
2
,但是当testPtr()
函数执行完后,ap
,bp
对象生命周期结束,那么它们所指向的内存空间智能指针计数都减一,最终空间计数都为1
- 此时不会析构,因为智能指针析构的判断依据是当前的内存空间指针指针计数为
0
,此时就会发生内存泄漏
解决办法就是让类里面的其中一个 shared_ptr
定义为 weak_ptr
,这样就打破了环的局面
记住
weak_ptr
不会让智能指针计数增加
struct TA {
weak_ptr<TB> bptr;
~TA() {
cout << "class TA is disstruct ..." << endl;
}
};
.... // 其余代码和上面一样
TA1 use_count: 1
TB1 use_count: 1
TA2 use_count: 2
TB2 use_count: 1
class TB is disstruct ...
class TA is disstruct ...
分析这个如何打破的
weak_ptr
不会让智能指针计数增加,所以最终结果就是执行结果所示- 当
testPtr()
函数执行完毕,ap
,bp
对象生命周期结束,对应的内存空间计数减一,此时bp
所指向的空间智能指针计数为0
,所以那块空间被释放,调用了析构函数,所以输出了class TB is disstruct ...
- 由于
TB
被析构,那么内部成员aptr
也被析构,那么所指向的TA
的空间的智能指针计数再减一,此时为0
,就调用了TA
的析构函数,空间释放,输出class TA is disstruct ...
可能还有小伙伴考虑为啥不直接删除其中一个
shared_ptr
成员变量,我也考虑到了,hh 还没想明白,但是需要知道shared_ptr
是有循环引用风险的这里需注意,weak_ptr
是可以打破的 然后还能作为旁观者,应该是有它妙处的,以后遇到就明白了,不深究了
说明:本文是在 https://subingwen.cn/ 学习过程中的总结,这个 up 主 B 站讲得很好 !!