15 RAII、智能指针⚓︎
约 921 个字 108 行代码 预计阅读时间 6 分钟
RAII⚓︎
例外 (exception) 是一种应对出现于代码中的错误的一种方式,它们会被“抛出来”。我们可以设计代码捕捉这些例外:
try
{
// code that we check for exceptions
}
catch([exception type] e1)
{
// behavior when we encounter an error
}
catch([other exception type] e2)
{
// "else if"
}
catch
{
// "else"
// catch-all
}
引入
这段代码有多少“代码路径”(code path)(简单理解为运行时代码会报错的地方)
23 条!
- 这是一个
Pet
类的拷贝构造函数,它可能会出错 \(\rightarrow\) 1 - 临时字符串(有 5 个常量字符串)的构造函数可能会出错 \(\rightarrow\) 5
- 调用成员函数会出错(1 次
.type()
,2 次.lastName()
、3 次firstName()
)\(\rightarrow\) 6 - 假设所有运算符(10 个)都是用户定义的重载运算符,它们都有可能会出错 \(\rightarrow\) 10
- 返回字符串的拷贝构造函数会出错 \(\rightarrow\) 1
假设我在原来代码基础上加了一些东西:
std::string returnNameCheckPawsome(Pet p)
{
Pet* p = new Pet(petId);
if (p.type() == "Dog" || p.firstName() == "Fluffy")
{
std::cout << p.firstName() << " " << p.lastName() << " is paw-some!" << '\n';
}
delete p;
return p.firstName() + " " + p.lastName();
}
如果在高光的两条语句的中间区域内有代码在运行时出错,那么会导致内存的泄露,后果十分严重。
下面列出的几样东西需要在获得资源后记得释放:
Acquire | Release | |
---|---|---|
Heap memory | new |
delete |
Files | open |
close |
Locks | try_lock |
unlock |
Sockets | socket |
close |
当使用这些东西时发生例外的时候,我们该如何正确释放它们呢?这里就要引入 RAII 的概念。RAII 全称 Resource Acquisition is Initialization(资源获取即初始化
- 类(对象)所需的所有资源需要用构造函数获取
- 类(对象)所需的所有资源需要用析构函数释放
一句话概括为:资源的有效期 = 持有资源的对象的生命周期。
反例
下面这两段代码均不符合 RAII 准则:
void printFile()
{
ifstream input;
input.open("hamlet.txt");
}
string line;
while (getLine(input, line))
{
std::cout << line << std::endl;
}
input.close();
问题:ifstream
的开关没有用到构造函数和析构函数
智能指针⚓︎
对于锁,我们在前面反例的代码 2 中使用 lock_guard
来实现 RAII 准则;而对于内存,也有类似的指针“包装”,它们称为智能指针,有以下几种类型:
std::unique_ptr
:占取资源的唯一指针,无法被复制std::shared_ptr
:能够被复制,当底层内存离开作用域范围便被释放std::weak_ptr
:用于减轻循环依赖 (circular dependenccies) 问题的指针
这是原来使用指针的操作,它不符合 RAII 原则:
用上智能指针后,它就变成这样了:
在使用 std::unique_ptr
指针的时候请不要进行复制操作。如果在析构函数调用后还对该指针进行复制操作的话,那么该指针的副本会指向一块未分配的内存,从而造成不良后果。若要避免这一问题,需要用到 std::share_ptr
指针,它能够确保指针及其副本在离开作用域范围后再解除分配的内存,从而避免上述意外的发生。
智能指针的初始化:
std::unique_str<T> uniquePtr = std::make_unique<T>();
std::shared_ptr<T> sharedPtr = std::make_shared<T>();
std::weak_ptr<T> wp = sharedPtr;
这是一段使用智能指针的错误示范:
#include <iostream>
#include <memory>
class B;
class A
{
std::shared_ptr<B> ptr_to_b;
~A()
{
std::cout << "All of A's resources deallocated" << std::endl;
}
};
class B
{
std::shared_ptr<A> ptr_to_a;
~B()
{
std::cout << "All of B's resources deallocated" << std::endl;
}
};
int main()
{
std::shared_ptr<A> shared_ptr_to_a = std::make_shared<A>();
std::shared_ptr<B> shared_ptr_to_b = std::make_shared<B>();
a->ptr_to_b = shared_ptr_to_b;
b->ptr_to_a = shared_ptr_to_a;
return 0;
}
这段代码的问题在于:A
的实例 a
和 B
的实例 b
互相保存了对方的共享指针,导致这两个指针无法被正确地解除引用
解决方案:在 class B
的声明中,将 std::shared_ptr<A> ptr_to_a;
一句换为 std::weak_ptr<A> ptr_to_a;
评论区