Last updated on 8 months ago
智能指针实现原理?
智能指针(Smart Pointers)是一种用于管理动态分配的内存资源的 C++ 类模板,不是指某个关键字,确保在不再需要时自动释放所持有的资源,从而避免内存泄漏和悬空指针等内存管理问题; 这个类里面通过引用计数 来跟踪指向同一对象的指针数量,每当一个智能指针指向对象时,对象的引用计数会增加;当智能指针超出作用域或被销毁时,引用计数会减少。当引用计数为零时,表示没有指针指向对象,可以安全地释放对象的内存。
std::shared_ptr
:共享式智能指针,允许多个指针共享同一个资源,这个资源可以被其他shared_ptr 引用,所以需要使用引用计数来管理资源的生命周期。
std::unique_ptr
:独占式智能指针,不能被复制,只能移动所有权,由于unique_ptr是独占所指向对象的所有权,不能共享,所以不使用引用计数,使用独占性质来管理对象的生命周期。
std::weak_ptr
:弱引用智能指针,可以解决 std::shared_ptr
的循环引用问题,不会增加资源的引用计数,不会延长资源的生命周期。
智能指针是线程安全吗?
先说下什么是线程安全?
线程安全指某个函数、函数库在多线程环境中被调用时,能够正确地处理多个线程之间的公用变量,使程序功能正确完成;
举一个线程操作不安全的例子:
10个线程,对global_counter 自加,期望值是 200000 ;
我们都知道类似 i++的操作,底层涉及了 读取、修改和写回多个步骤,所以这 ++操作 并不是线程安全的;
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
| #include <stdio.h> #include <pthread.h>
#define NUM_THREADS 10 #define ITERATIONS 10000
int global_counter = 0;
void *thread_function(void *arg) { for (int i = 0; i < ITERATIONS; ++i) { global_counter++; } return NULL; } int main() { pthread_t threads[NUM_THREADS]; for (int i = 0; i < NUM_THREADS; ++i) { pthread_create(&threads[i], NULL, thread_function, NULL); } for (int i = 0; i < NUM_THREADS; ++i) { pthread_join(threads[i], NULL); } printf("Global counter value: %d\n", global_counter);
return 0; }
|
结果为 217250,和预期的 200000 不一致
1 2 3 4
| root@ubuntu:/home# gcc thread.c -o thread -pthread root@ubuntu:/home# ./thread Global counter value: 217250 root@ubuntu:/home#
|
智能指针类的里面的关键操作
从智能指针的作用来看,只是管理内存的生命周期,并不负责竞争和同步问题
从智能指针内部类实现来看,最为关键是 引用计数的自增,多线程场景,使用shared_ptr,这个时候是有可能? 为了确定下,直接用gdb 进去看下(我猜测时在 拷贝构造的时候会调用计数+1操作)
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
| #include <iostream> #include <memory> #include <vector>
int main() { std::shared_ptr<std::vector<int>> ptr1 = std::make_shared<std::vector<int>>(5, 10);
std::shared_ptr<std::vector<int>> ptr2 = ptr1;
std::cout << "ptr1 引用计数: " << ptr1.use_count() << std::endl; std::cout << "ptr2 引用计数: " << ptr2.use_count() << std::endl;
std::shared_ptr<std::vector<int>> ptr3 = ptr1;
std::cout << "ptr1 引用计数: " << ptr1.use_count() << std::endl; std::cout << "ptr2 引用计数: " << ptr2.use_count() << std::endl; std::cout << "ptr3 引用计数: " << ptr3.use_count() << std::endl; return 0; }
|
17行打两个断点,
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 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
| (gdb) main () at shareptr.cc:13 13 std::cout << "ptr1 引用计数: " << ptr1.use_count() << std::endl; (gdb) ptr1 引用计数: 2 14 std::cout << "ptr2 引用计数: " << ptr2.use_count() << std::endl; (gdb) ptr2 引用计数: 2
17 std::shared_ptr<std::vector<int>> ptr3 = ptr1; (gdb) s std::shared_ptr<std::vector<int, std::allocator<int> > >::shared_ptr ( this=0x7fffffffe490) at /usr/include/c++/7/bits/shared_ptr.h:119 119 shared_ptr(const shared_ptr&) noexcept = default;
(gdb) s std::__shared_ptr<std::vector<int, std::allocator<int> >, (__gnu_cxx::_Lock_policy)2>::__shared_ptr (this=0x7fffffffe490) at /usr/include/c++/7/bits/shared_ptr_base.h:1121 1121 __shared_ptr(const __shared_ptr&) noexcept = default;
(gdb) s std::__shared_count<(__gnu_cxx::_Lock_policy)2>::__shared_count ( this=0x7fffffffe498, __r=...) at /usr/include/c++/7/bits/shared_ptr_base.h:688 688 : _M_pi(__r._M_pi)
(gdb) n 690 if (_M_pi != 0)
(gdb) p *_M_pi $2 = {<std::_Mutex_base<(__gnu_cxx::_Lock_policy)2>> = {<No data fields>}, _vptr._Sp_counted_base = 0x555555758c80 <vtable for std::_Sp_counted_ptr_inplace<std::vector<int, std::allocator<int> >, std::allocator<std::vector<int, std::allocator<int> > >, (__gnu_cxx::_Lock_policy)2>+16>, _M_use_count = 2, _M_weak_count = 1} (gdb) n 691 _M_pi->_M_add_ref_copy();
(gdb) s std::_Sp_counted_base<(__gnu_cxx::_Lock_policy)2>::_M_add_ref_copy ( this=0x55555576be70) at /usr/include/c++/7/bits/shared_ptr_base.h:138 138 { __gnu_cxx::__atomic_add_dispatch(&_M_use_count, 1); }
|
从代码上看,引用计数累计时原子操作,所以说明 shared_ptr 内部操作是线程安全的,但是不能保证多线程场景下用 shared_ptr 管理内存数据的就一定是线程安全性