在 C++ 中没有垃圾回收机制,必须自己释放分配的内存,否则会造成内存泄露。解决这个问题最有效的方法是使用智能指针 smart pointer
。智能指针是存储了指向动态分配(堆内存)对象指针的类,用于生存期的控制,能够确保在离开指针所在作用域时自动地销毁动态分配的对象,防止内存泄露。智能指针的实现技术是引用计数,每使用一次内部引用计数加 1,每析构一次内部的引用计数减 1,减为 0 时删除所指向的堆内存。
C++11 中提供了三种智能指针,使用这些智能指针需要引入头文件 <memory>
:
std::shared_ptr
:共享的智能指针
std::unique_ptr
:独占的智能指针
std::weak_ptr
:弱引用的智能指针,它不共享指针,不能操作资源,用来监视 shared_ptr。
shared_ptr 的初始化
共享智能指针是指多个智能指针可以同时管理同一块有效的内存,共享智能指针 shared_ptr
是一个模板类。
共享智能指针对象初始化完毕之后就指向了需要管理的那块堆内存,如果想要查看当前有多少个智能指针同时管理着这块内存可以使用共享智能指针提供的一个成员函数 use_count
,函数原型如下:
1 2
| long use_count() const noexcept;
|
初始化有三种方式:通过构造函数、std::make_shared
函数、reset
方法。
通过构造函数初始化
1 2
| std::shared_ptr<T> 智能指针名字(创建堆内存);
|
示例代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| #include <iostream> #include <memory> using namespace std;
int main() { shared_ptr<int> ptr1(new int(520)); cout << "ptr1管理的内存引用计数: " << ptr1.use_count() << endl; shared_ptr<char> ptr2(new char[12]); cout << "ptr2管理的内存引用计数: " << ptr2.use_count() << endl; shared_ptr<int> ptr3; cout << "ptr3管理的内存引用计数: " << ptr3.use_count() << endl; shared_ptr<int> ptr4(nullptr); cout << "ptr4管理的内存引用计数: " << ptr4.use_count() << endl;
return 0; }
|
如果智能指针被初始化了一块有效内存,那么这块内存的引用计数 + 1;如果智能指针没有被初始化或者被初始化为 nullptr 空指针,引用计数不会 + 1。另外,不要使用一个原始指针初始化多个 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
| #include <iostream> #include <memory> using namespace std;
int main() { shared_ptr<int> ptr1(new int(520)); cout << "ptr1管理的内存引用计数: " << ptr1.use_count() << endl; shared_ptr<int> ptr2(ptr1); cout << "ptr2管理的内存引用计数: " << ptr2.use_count() << endl; shared_ptr<int> ptr3 = ptr1; cout << "ptr3管理的内存引用计数: " << ptr3.use_count() << endl; shared_ptr<int> ptr4(std::move(ptr1)); cout << "ptr4管理的内存引用计数: " << ptr4.use_count() << endl; std::shared_ptr<int> ptr5 = std::move(ptr2); cout << "ptr5管理的内存引用计数: " << ptr5.use_count() << endl;
return 0; }
|
如果使用拷贝的方式初始化共享智能指针对象,这两个对象会管理同一块堆内存,堆内存对应的引用计数也会增加;如果使用移动的方式初始智能指针对象,只是转让了内存的所有权,管理内存的对象并不会增加,内存的引用计数不会变化。
通过 std::make_shared 初始化
通过 C++ 提供的 std::make_shared()
可以完成内存对象的创建并将其初始化给智能指针,函数原型如下:
1 2 3 4 5 6 7
| template< class T, class... Args > shared_ptr<T> make_shared( Args&&... args );
|
示例代码如下:
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
| #include <iostream> #include <string> #include <memory> using namespace std;
class Test { public: Test() { cout << "construct, Test()" << endl; } Test(int x) { cout << "construct, Test(int x), x = " << x << endl; } Test(string str) { cout << "construct, Test(string str), str = " << str << endl; } ~Test() { cout << "destruct, ~Test()" << endl; } };
int main() { shared_ptr<int> ptr1 = make_shared<int>(520); cout << "ptr1管理的内存引用计数:" << ptr1.use_count() << endl; shared_ptr<Test> ptr2 = make_shared<Test>(); cout << "ptr2管理的内存引用计数:" << ptr2.use_count() << endl; shared_ptr<Test> ptr3 = make_shared<Test>(520); cout << "ptr3管理的内存引用计数:" << ptr3.use_count() << endl; shared_ptr<Test> ptr4 = make_shared<Test>("zhangsan"); cout << "ptr4管理的内存引用计数:" << ptr4.use_count() << endl; return 0; }
|
使用 std::make_shared()
模板函数可以完成内存地址的创建,并将最终得到的内存地址传递给共享智能指针对象管理。如果申请的内存是普通类型,通过函数的 ()
可以完成地址的初始化;如果要创建一个类对象,函数的 ()
内部需要指定构造对象需要的参数(类中构造函数的参数)。
通过 reset 方法初始化
共享智能指针类提供的 std::shared_ptr::reset
方法,函数原型如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| void reset() noexcept;
template< class Y > void reset( Y* ptr );
template< class Y, class Deleter > void reset( Y* ptr, Deleter d );
template< class Y, class Deleter, class Alloc > void reset( Y* ptr, Deleter d, Alloc 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
| #include <iostream> #include <string> #include <memory> using namespace std;
int main() { shared_ptr<int> ptr1 = make_shared<int>(520); shared_ptr<int> ptr2 = ptr1; shared_ptr<int> ptr3 = ptr1; shared_ptr<int> ptr4 = ptr1; cout << "ptr1管理的内存引用计数: " << ptr1.use_count() << endl; cout << "ptr2管理的内存引用计数: " << ptr2.use_count() << endl; cout << "ptr3管理的内存引用计数: " << ptr3.use_count() << endl; cout << "ptr4管理的内存引用计数: " << ptr4.use_count() << endl;
ptr4.reset(); cout << "ptr1管理的内存引用计数: " << ptr1.use_count() << endl; cout << "ptr2管理的内存引用计数: " << ptr2.use_count() << endl; cout << "ptr3管理的内存引用计数: " << ptr3.use_count() << endl; cout << "ptr4管理的内存引用计数: " << ptr4.use_count() << endl;
shared_ptr<int> ptr5; ptr5.reset(new int(250)); cout << "ptr5管理的内存引用计数: " << ptr5.use_count() << endl;
return 0; }
|
对于一个未初始化的共享智能指针,可以通过 reset
方法来初始化。当智能指针中有值的时候,调用 reset
会使引用计数减 1。
获取原始指针
对于基础数据类型来说,通过操作智能指针和操作智能指针管理的内存效果是一样的,可以直接完成数据的读写。但是如果共享智能指针管理的是一个对象,那么需要取出原始内存的地址再操作,可以调用共享智能指针类提供的 get()
方法得到原始地址,函数原型如下:
1
| T* get() const noexcept;
|
示例代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| #include <iostream> #include <cstring> #include <memory> using namespace std;
int main() { int len = 128; shared_ptr<char> ptr(new char[len]); char* add = ptr.get(); memset(add, 0, len); strcpy(add, "zhangsan"); cout << "string: " << add << endl; shared_ptr<int> p(new int); *p = 100; cout << *p.get() << " " << *p << endl; return 0; }
|
指定删除器
当智能指针管理的内存对应的引用计数变为 0 时,这块内存就会被智能指针析构。另外,在初始化智能指针的时候也可以指定删除动作,这个删除操作对应的函数被称为删除器,这个删除器函数其实是一个回调函数,我们只需要实现,其调用是由智能指针完成的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| #include <iostream> #include <memory> using namespace std;
void deleteIntPtr(int* p) { delete p; cout << "int 型内存被释放了..." << endl; }
int main() { shared_ptr<int> ptr(new int(250), deleteIntPtr); return 0; }
|
删除器函数也可以是 lambda 表达式,示例代码修改如下:
1 2 3 4 5 6 7 8 9 10 11 12 13
| #include <iostream> #include <memory> using namespace std;
int main() { shared_ptr<int> ptr(new int(250), [](int* p) { delete p; cout << "int 型内存被释放了..." << endl; }); return 0; }
|
示例代码中 lambda 表达式的参数就是智能指针管理的内存的地址,有了这个地址之后函数体内部就可以完成删除操作。
在 C++11 中使用 shared_ptr
管理动态数组时需要指定删除器,因为 std::shared_ptr
的默认删除器不支持数组对象,示例代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13
| #include <iostream> #include <memory> using namespace std;
int main() { shared_ptr<int> ptr(new int[10], [](int* p) { delete[] p; cout << "int[] 内存被释放了..." << endl; }); return 0; }
|
在删除数组内存时,除了自己编写删除器,也可以使用 C++ 提供的 std::default_delete<T>()
函数作为删除器,这个函数内部的删除功能也是通过调用 delete
来实现的,将模板类型 T
指定为需要释放的类型。示例代码如下:
1 2 3 4 5 6 7 8 9 10
| #include <iostream> #include <memory> using namespace std;
int main() { shared_ptr<int> ptr(new int[10], default_delete<int[]>()); return 0; }
|
另外,还可以自己封装一个 make_shared_array
方法来让 shared_ptr
支持数组。示例代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| #include <iostream> #include <memory> using namespace std;
template <typename T> shared_ptr<T> make_share_array(size_t size) { return shared_ptr<T>(new T[size], default_delete<T[]>()); }
int main() { shared_ptr<int> ptr1 = make_share_array<int>(10); cout << ptr1.use_count() << endl; shared_ptr<char> ptr2 = make_share_array<char>(128); cout << ptr2.use_count() << endl; return 0; }
|
参考资料
https://subingwen.cn/cpp/shared_ptr