智能指针
注意智能指针管理的是动态分配的内存,即堆区的数据,栈上的会有问题
shared_ptr 共享
共享指针(是指可以多个 shared_ptr
指向同一块内存,进行管理)
好处:
- 方便调用,重要的是防止内存泄漏
- 因为我们自己定义指针,
new
出空间,就得手动释放,但是智能指针是一个模板类,封装好了的,当不需要的时候,会在智能指针生命周期结束后自动释放内存
- 比如定义在函数里,函数结束,它就结束,自动释放内存,还比如它不再指向内存空间了,就自动释放内存,指向空
初始化
默认构造函数
1 2 3
| std::shared_ptr<T> 智能指针名字(创建堆内存); shared_ptr<int> ptr1(new int(520));
|
std::make_shared
1 2 3 4 5 6 7 8 9 10 11
| 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
1 2 3 4 5 6 7 8 9 10 11 12
| 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
:内部存储所用的分配器
==综合示例==
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| 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);
shared_ptr<int> ptr6 = make_shared<int>(520); shared_ptr<Test> ptr7 = make_shared<Test>("zhangsan"); shared_ptr<Test> ptr8 = make_shared<Test>(520);
ptr4.reset(); ptr5.reset(new int(250)); ptr8.reset(new int(250)); }
|
内部函数
1 2 3
| long use_count() const noexcept; ptr.use_count;
|
1 2 3 4 5
| T* get() const noexcept;
shared_ptr<int> ptr1(new int(520)); int *p = ptr1.get();
|
1 2
| Test *t = ptr.get(); t->setvalue(100);
|
- 上面
reset
和内部函数方法使用 .
,而这里使用 ->
本质是内部重载了这些符号,这是个类模板,这些方法是内部成员函数,使用 .
- 而指针的使用是
->
,是为了方便我们把他就当成指针运算,方便记忆和使用,所以内部重载了->
,使其和指针一样
注意点
1 2 3 4 5
| int *a = new int(10); shared_ptr<int>ptr(a); *ptr = 100; cout << *a << endl; cout << ptr.use_count();
|
- 说明这个只是统计智能指针的数量,使用智能指针修改数据,实际也可改变,说明指针确实在管理内存
指定删除器
智能指针不指定删除器,会自动调用内部的删除器,将其内存释放,但也可指定
1 2 3 4
| shared_ptr<int> ptr(new int(250), [](int *t){ delete t; });
|
但是数组不具有默认的删除器,因此需指定(C++11 不会,C++11 以后可以了)
1 2 3
| shared_ptr<int> ptr(new int[10], [](int* p) { delete []p; });
|
1 2 3
| std::default_delete<T>();
shared_ptr<int> ptr(new int[10], default_delete<int []>());
|
unique_ptr 独占
只允许一个智能指针管理内存,但是不包括普通指针不能管理
初始化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| 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(); ptr1.reset(); ptr2.reset(new int(250)); }
|
注意
1 2 3 4 5 6 7 8
| int main() { int *a = new int(10); unique_ptr<int>ptr(a); *ptr = 100; cout << *a << endl; }
|
指定删除器
语法和共享的类似
展示区别
1 2 3 4 5 6 7 8 9
| 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
表达式,就使用一个函数,然后就能指定类型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| 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); }
|
1 2 3
|
unique_ptr<int[]> ptr(new int [3]);
|
weak_ptr 弱引用
它不共享指针,不能操作资源,是用来监视 shared_ptr
的
- 弱引用智能指针
std::weak_ptr
可以看做是 shared_ptr
的助手,它不管理 shared_ptr
内部的指针
std::weak_ptr
没有重载操作符 *
和 ->
,因为它不共享指针,不能操作资源,所以它的构造不会增加引用计数(指向当前内存空间的智能指针数量),析构也不会减少引用计数,它的主要作用就是作为一个旁观者监视 shared_ptr
中管理的资源是否存在
初始化
语法:
1 2 3 4 5 6 7
| constexpr weak_ptr() noexcept;
weak_ptr (const weak_ptr& x) noexcept; template <class T> weak_ptr (const weak_ptr<T>& x) noexcept;
template <class T> weak_ptr (const shared_ptr<T>& x) noexcept;
|
1 2 3 4 5 6 7
| shared_ptr<int> sp(new int (10));
weak_ptr<int> wp1 = new int (11); weak_ptr<int> wp2(sp); weak_ptr<int> wp3(wp2); weak_ptr<int> wp4 = sp; weak_ptr<int> wp5 = wp3;
|
内部函数
use_count()
1 2
| long int use_count() const noexcept;
|
weak_ptr
只是监测资源,并不管理资源,所以不会导致计数加一
expired()
1 2
| bool expired() const noexcept;
|
示例
1 2 3 4 5 6
| 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 2
| 1. weak is not expired 2. weak is expired
|
lock()
1 2
| shared_ptr<element_type> lock() const noexcept;
|
reset()
解决 shared_ptr 遗留的问题
不能使用一个原始地址初始化多个共享智能指针
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| 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; }
|
执行结果:
1 2 3 4
| use_count: 1 use_count: 1 析构了 析构了
|
- 调用了两次析构,一个是
ptr1
,一个是 ptr2
- 这就有问题,一块空间释放了两次
- 这里的错误是使用了原始地址
t
去初始化了多个共享智能指针,正确写法是 shared_ptr<Test> ptr2(ptr1);
- 还有人可能疑惑第一行
Test *t = new Test
为啥没有析构函数调用
1 2 3 4 5 6
| Test *t = new Test; delete t;
Test t;
|
函数不能返回管理了 this 的共享智能指针
需求是通过成员函数返回管理当前对象的共享智能指针
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| 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; }
|
1 2 3 4
| 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
1 2
| std::enable_shared_from_this<T> shared_from_this()
|
- 它的本质实现是使用了
weak_ptr
里的 lock()
方法实现,返回 shared_ptr
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| 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; }
|
执行结果:
1 2 3
| 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
是安全的。
解决循环引用问题
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| 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(); }
|
1 2 3 4
| 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
不会让智能指针计数增加
1 2 3 4 5 6 7
| struct TA { weak_ptr<TB> bptr; ~TA() { cout << "class TA is disstruct ..." << endl; } }; ....
|
1 2 3 4 5 6
| 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 站讲得很好 !!


xingzhu
keep trying!keep doing!believe in yourself!
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 星竹!