11.RAII and smart pointer

一、资源管理与RAII原则

在C++编程中,资源泄漏是一个常见问题,尤其是在异常发生时。为了确保资源总是被正确释放,C++引入了RAII(Resource Acquisition Is Initialization)原则。

1.1 RAII原则

RAII是一种资源管理模型,确保使用对象自动管理资源。按照RAII原则,资源在对象构造时获取,在对象析构时释放。这样可以保证即使在发生异常的情况下资源也能被正确释放。

更直观的名字是CADRE(Constructor Acquires, Destructor Releases),强调构造函数获取资源,析构函数释放资源的概念。

1.2 为什么使用RAII?

  • 对象在创建后立即可用,无需额外的初始化步骤。
  • 对象永远不处于半有效状态,要么完全管理其资源,要么在异常发生时自动释放资源。
  • 析构函数总是在对象离开作用域时调用,因此资源总会被释放。

1.3 RAII的实践

以下几种都会出现RAII问题:

文件操作示例

不遵循RAII原则的代码示例与遵循RAII原则的代码示例对比:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 不遵循RAII
void printFile() {
ifstream input;
input.open("hamlet.txt");
string line;
while (getline(input, line)) {
cout << line << endl;
}
input.close();
}

// 遵循RAII
void printFile() {
ifstream input("hamlet.txt");
string line;
while (getline(input, line)) {
cout << line << endl;
}
// 不需要调用close,ifstream的析构函数会自动关闭文件
}

锁的管理示例

使用lock_guard自动管理互斥锁,避免忘记释放锁或异常导致锁未释放:

1
2
3
4
5
6
7
8
9
10
11
12
// 不遵循RAII
void cleanDatabase(mutex &databaseLock, map<int, int> &database) {
databaseLock.lock();
// 修改数据库
databaseLock.unlock();
}

// 遵循RAII
void cleanDatabase(mutex &databaseLock, map<int, int> &database) {
lock_guard<mutex> lock(databaseLock);
// 修改数据库,即使发生异常也不需要手动解锁
}

lock_guard的简化实现:

1
2
3
4
5
6
7
class lock_guard {
public:
explicit lock_guard(mutex &lock) : acquired_lock(lock) { acquired_lock.lock(); }
~lock_guard() { acquired_lock.unlock(); }
private:
mutex &acquired_lock;
};

二、RAII 和智能指针

2.1 非RAII示例

使用原始指针管理内存,存在异常时资源泄露风险:

1
2
3
4
5
void rawPtrFn() {
Node* n = new Node;
// 若异常抛出,n所指向的内存将不会被释放
delete n;
}

2.2 C++中的智能指针

C++提供了几种“智能”指针来自动管理资源,遵循RAII原则:

  • std::unique_ptr:独占所指资源,不可复制,但可以移动。
  • std::shared_ptr:共享所指资源,可复制,引用计数为零时释放资源。
  • std::weak_ptr:不拥有资源,用于解决std::shared_ptr间的循环引用问题。

2.3 std::unique_ptr

std::unique_ptr独占其所指向的资源,并在析构时释放该资源。它不能被复制,防止多个指针同时管理同一资源。

1
2
3
4
void useUniquePtr() {
std::unique_ptr<Node> n(new Node);
// 使用n
} // n所指向的Node对象在此处被自动释放

尝试复制std::unique_ptr会导致编译错误,因为其复制构造函数和复制赋值操作符被删除。

假设可以复制std::unique_ptr,会导致的错误:

1.make a unique ptr
2.make a copy of this pointer
3.y goes out of scope
4.crash

2.4 std::shared_ptr

std::shared_ptr允许多个指针共享对同一资源的拥有权。当最后一个shared_ptr被销毁时,资源才会被释放。

1
2
3
4
5
6
7
8
9
{
std::shared_ptr<int> p1(new int);
{
std::shared_ptr<int> p2 = p1;
// p1 和 p2 均有效
}
// 仅p1有效
}
// 此处p1被销毁,其所指资源被释放

2.5 std::weak_ptr

std::weak_ptr用于指向std::shared_ptr管理的对象,但不影响对象的引用计数。它主要用于解决std::shared_ptr之间的循环引用问题。

2.6 智能指针创建函数

推荐使用标准库提供的智能指针创建函数,因为它们可以更安全地分配资源,并减少代码中的裸new操作。

  • std::make_unique<T>(args...):创建std::unique_ptr
  • std::make_shared<T>(args...):创建std::shared_ptr
1
2
std::unique_ptr<T> up = std::make_unique<T>();
std::shared_ptr<T> sp = std::make_shared<T>();

总是优先使用std::make_unique()std::make_shared()创建智能指针,它们不仅提供了类型安全和异常安全保证,还可以简化代码并提高可读性。


11.RAII and smart pointer
https://ci-tz.github.io/2024/02/12/11-RAII-and-smart-pointer/
作者
次天钊
发布于
2024年2月12日
许可协议