C++杂记——RAII (Resource Acquisition Is Initialization)
什么是RAII
RAII(Resource Acquisition Is Initialization,资源获取即初始化)是一种C++编程习惯和原则,旨在通过资源的生命周期管理来保证资源的安全和有效使用。RAII的核心思想是将资源的获取和释放绑定到对象的生命周期。当对象被创建时,它会分配相应的资源;当对象的生命周期结束(例如超出作用域时),其析构函数会自动被调用,从而释放资源。
它使得我们得以规避“裸new操作”和“裸delete操作”的风险,避免裸new和裸delete可以使我们的代码远离各种潜在风险,避免资源泄漏。
RAII的优势
- 自动资源管理:通过RAII,程序员不需要手动释放资源,减少了内存泄漏等问题的出现。
- 异常安全:RAII使得在出现异常时,资源仍能安全释放,避免了资源的泄露。
- 清晰的资源控制:通过对象的生命周期管理,代码更加整洁,意图更加明确。
比如:网络套接字、互斥锁、文件句柄和内存等等,它们属于系统资源。由于系统的资源是有限的,所以,我们在编程使用系统资源时,都必须遵循一个步骤:
- 申请资源;
- 使用资源;
- 释放资源。
第一步和第二步缺一不可,因为资源必须要申请才能使用的,使用完成以后,必须要释放,如果不释放的话,就会造成资源泄漏。
RAII 原理
当局部变量离开其作用域时,这个变量也就被销毁了;当这个变量是类对象时,这个时候就会自动调用这个类的析构函数。
由于系统的资源不具有自动释放的功能,而C++中的类具有自动调用析构函数的功能。如果把资源用类进行封装起来,对资源操作都封装在类的内部,在析构函数中进行释放资源。当定义的局部变量的生命结束时,它的析构函数就会自动的被调用,如此,就不用显式调用释放资源的操作了。
RAII的应用
STL(标准模板库)在设计上充分利用了RAII原则。以下是RAII在STL中的几个典型应用:
-
std::vector 和 std::string:
这些容器自动管理内存。在创建一个std::vector或std::string对象时,它们会分配适量的内存并在对象析构时自动释放。
当std::vector中的元素超出它的生命周期时,它的析构函数会依次调用每个元素的析构函数,从而确保没有资源泄漏。 -
智能指针:
STL中的智能指针(如std::unique_ptr和std::shared_ptr)也遵循RAII原则。它们在对象被创建时申请资源(例如动态分配的内存),并在对象被销毁时自动释放资源。
智能指针提供了一种管理动态对象生命周期的安全方式,避免了传统指针可能导致的内存管理错误。 -
容器迭代器:
STL中的迭代器通常是用对象来表示的,这些对象也遵循RAII原则。比如std::lock_guard在锁的使用中,保证锁在使用时被持有,而当锁对象超出作用域时,锁会自动释放。 -
互斥体包装器
std::lock_guard是一个互斥体封装器,它提供了一个方便的RAII风格机制,用于在作用域块的持续时间内拥有一个互斥体。
当一个lock_guard对象被创建时,它会尝试获取它被赋予的互斥体的所有权。当控制离开创建lock_guard对象的作用域时,lock_guard会被销毁,互斥体也会被释放。
例子
自动释放资源
自动计算函数耗时
#ifndef _CODE_SNIPPET_RAII_H_
#define _CODE_SNIPPET_RAII_H_
#include <sys/time.h>
#include <chrono>
#include <ctime>
#include <iostream>
#include <string>
using llong = long long;
using namespace std::chrono;
using std::cout;
using std::endl;
class TimerLog
{
public:
TimerLog(const std::string tag)
{
m_begin_ = high_resolution_clock::now();
m_tag_ = tag;
}
void Reset()
{
m_begin_ = high_resolution_clock::now();
}
llong Elapsed()
{
return static_cast<llong>(duration_cast<std::chrono::milliseconds>(
high_resolution_clock::now() - m_begin_)
.count());
}
~TimerLog()
{
auto time = duration_cast<std::chrono::milliseconds>(
std::chrono::high_resolution_clock::now() - m_begin_)
.count();
std::cout << "time { " << m_tag_ << " } " << static_cast<double>(time)
<< " ms" << std::endl;
}
private:
std::chrono::time_point<std::chrono::high_resolution_clock> m_begin_;
std::string m_tag_;
};
#define CALL_SCOPE_TIME(x) TimerLog t(x)
void TestTimerLog();
#endif
#include "RAII.h"
#include <thread>
void TestTimerLog()
{
auto func = []() {
for (int i = 0; i < 2; ++i) {
cout << "i " << i << endl;
std::this_thread::sleep_for(std::chrono::milliseconds(1));
}
};
{
CALL_SCOPE_TIME("func");
func();
}
}
测试结果:
$ ./CPP/cpp_snippet
i 0
i 1
time { func } 2 ms
注意
虑到封装的是资源,资源很多时候是不具备拷贝语义的,所以,在实际实现过程中需要控制其复制过程(比如lock_guard直接不可复制),让资源的一切操作都在自己的控制当中。
总结
RAII是一种有效的资源管理机制,极大地提升了C++程序的安全性和稳定性。在STL中,RAII得到广泛应用,通过智能指针、容器及其迭代器等技术,使得资源管理变得更加简单和安全。这不仅减少了人为错误,也支持了异常安全的编程习惯。

浙公网安备 33010602011771号