ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

【C++】 智能指针RAII

【C++】 智能指针RAII

一.什么是智能指针?

智能指针是根据RAII的设计思路实现的,RAII(Resource Acquisition Is Initialization)资源获取即初始化,智能指针是由类实现的,是一种用于自动管理内存空间资源的工具,它能在程序结束时自动释放那些未释放的资源,从而帮助我们管理指针,避免内存泄漏,这里的资源可以是内存、文件指针、网络连接、互斥锁等等

智能指针类除了满足RAII的设计思路,还要方便资源的访问,所以智能指针类还会想迭代器类一样,重载 operator*/operator->/operator[] 等运算符,方便访问资源

二.智能指针的使用

程序内存泄漏的原因是千奇百怪的,不是我们想预防就避免得了的,比如下面这段程序

struct A { A(int a) :_a(a) { } ~A() { cout << "delete" << endl;// 直观是否调用析构 } int _a; }; void func(int a, int b) { A* a1 = new A(a); A* a2 = new A(b); try { if (a == 10) { string s("error"); throw s; } cout << a + b << endl; } catch(const int& a) { cout << a << endl; } delete a1; delete a2; } int main() { try { func(10, 20); } catch (const string& s) { cout << s << endl; } return 0; }

我们运行这段代码就会发现,类A的析构函数并没有执行

当然,实践中的情况会更加复杂,这时候要想避免内存泄漏,我们就可以利用智能指针来解决

struct A { A(int a) :_a(a) { } ~A() { cout << "delete" << endl; } int _a; }; void func(int a, int b) { shared_ptr<A> a1(new A(a)); shared_ptr<A> a2(new A(b)); try { if (a == 10) { string s("error"); throw s; } cout << a + b << endl; } catch(const int& a) { cout << a << endl; } } int main() { try { func(10, 20); } catch (const string& s) { cout << s << endl; } return 0; }

可以看到在func函数栈帧销毁之前,a1, a2已经被析构了,这是因为在栈帧销毁前会将函数中的变量全部销毁,而智能指针的本质也是一个类,所以这样就能够避免内存泄漏

三.常见的智能指针

指针类型版本优点缺点应用场景
auto_ptrC++98发行版本早一块内存空间只有一个auto_ptr管理,极易导致越界访问问题,一般被禁止使用一般不使用
unique_ptrC++11与auto_ptr相比,不支持拷贝构造/赋值,支持移动构造/赋值一块内存空间同样只有一个unique_ptr管理一块内存空间只需一个智能指针管理
shared_ptrC++11用引用计数支持多个shared_ptr一起管理一块内存空间在特定情况会出现循环引用问题,导致内存泄漏大多应用场景都适用
weak_ptrC++11辅助shared_ptr,处理循环引用问题只能用来辅助shared_ptr避免shared_ptr出现循环引用问题

在常见的智能指针类型中,auto_ptr只需略作了解即可,由于槽点太多在实际中一般被禁止使用;

其中shared_ptr需要我们详细了解,在面试中shared_ptr也是面试官常问的问题,后面我们就来深入了解并模拟实现unique_ptr,shared_ptr,并用weak_ptr来处理循环引用问题

四.unique_ptr的使用并模拟实现

1.构造函数

int main() { unique_ptr<int> up1(new int(10)); // unique_ptr<int> up2 = new int(20));// error return 0; }

可以看到unique_ptr的构造函数只支持显式化传参,不支持隐式类型转化,所以我们要用explicit修饰构造函数

2.拷贝构造/赋值

int main() { unique_ptr<int> up1(new int(10)); // unique_ptr<int> up2(up1);// error unique_ptr<int> up3; // up3 = up1;//error return 0; }

首先我们可以看到拷贝构造/赋值都是不可调用的(详细原因在实现shared_ptr时说明),所以我们的思路也是不能实现拷贝构造/赋值

3.移动拷贝/赋值

int main() { unique_ptr<int> up1(new int(10)); //unique_ptr<int> up2(move(up1)); unique_ptr<int> up2; up2 = move(up1); cout << *up2 << endl; return 0; }

可以看到unique_ptr是支持移动构造/赋值的,并且交换后的up1指向的空间已释放

4.运算符重载

int main() { unique_ptr<pair<string, int>> up1(new pair<string, int>("C++", 250)); unique_ptr<int> up2(new int(10)); cout << up1->first << ":" << up1->second << endl; cout << *up2 << endl; if (up1) cout << "not nullptr" << endl; return 0; }

从上面测试中可以发现,我们要重载的运算符至少要有operator->, operator*, operator bool

5.模拟代码

template<class T> class my_unique_ptr { public: explicit my_unique_ptr(T* ptr = nullptr) :_ptr(ptr) { } my_unique_ptr(const my_unique_ptr& up) = delete; my_unique_ptr& operator=(const my_unique_ptr& up) = delete; my_unique_ptr(my_unique_ptr&& up) :_ptr(up._ptr) { up._ptr = nullptr; } my_unique_ptr& operator=(my_unique_ptr&& up) { if (up._ptr != _ptr) { delete _ptr; _ptr = up._ptr; up._ptr = nullptr; } return *this; } T& operator*() { return *_ptr; } T* operator->() { return _ptr; } operator bool() { return _ptr; } ~my_unique_ptr() { cout << "delete " << endl; delete _ptr; _ptr = nullptr; } private: T* _ptr; }; int main() { my_unique_ptr<pair<string, int>> up1(new pair<string, int>("C++", 250)); my_unique_ptr<int> up2(new int(10)); *up2 = 20; //my_unique_ptr<int> up3 = new int(20);// 不支持隐式类型转化 //my_unique_ptr<int> up4(up2);// 不支持拷贝构造 //my_unique_ptr<int> up5; //up5 = up1;// 不支持拷贝赋值 //my_unique_ptr<int> up6(new int(10)); //my_unique_ptr<int> up7(move(up6));// 移动构造 //my_unique_ptr<int> up8; //up8 = move(up6);// 移动赋值 cout << *up2 << endl;// operator* cout << up1->first << ":" << up1->second << endl;// operator-> if (up1)// operator bool cout << "not nullptr" << endl; return 0; }

五.智能指针的析构函数

我们都知道指针有很多用途,当我们不使用这个指针时,处理方法也有很多种:

但每个类只能有一个析构函数,那这时候我们可以定义一个function<void(type_name*)>类型的成员变量_del,再利用仿函数来初始化

那么下一篇就带来shared_ptr的详细实现过程

综上就是本期博客的所有内容,感谢阅读,也欢迎大家讨论纠错

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进