> 文章列表 > 15.C++中的RAII

15.C++中的RAII

15.C++中的RAII

文章目录

    • 1.何为RAII
    • 2.RAII型的类和使用
    • 参考资料

欢迎访问个人网络日志🌹🌹知行空间🌹🌹


1.何为RAII

RAIIResource Acquisition Is Initialization的缩写,是由C++之父Bjarne Stroustrup提出来的,直接翻译过来就是资源获取即初始化,是一个非常强大的编程概念。RAII理念是借助对象的作用域/生存周期来管理资源,因此也有呼声将其更名为Scope-Bound Resource Management。在这个概念中资源所指的不仅是内存,也可以指文件描述符,套接字,数据库句柄等。资源的生命周期等同于资源对象的作用域。RAIIC++中管理资源、避免内存泄漏的好方法。在C++中,在创建一个类对象时会自动调用类的构造函数,对象超出作用域时会自动调用析构函数。RAII的思想就是将资源与对象的生命周期绑定。

RAII可以总结为两点,

  • 将资源封装到类中
    • 构造函数获取资源并初始化类中的常量,否则要抛出异常
    • 释放资源,不抛异常
  • 通过RAII型的类的实例来使用资源
    • 资源的生命周期和类的实例的生命周期相同

带有open/close,lock/unlock,init/copyFrom/destroy成员函数的类通常不是RAII型的类。

C++中,STL中的很多类都遵守RAII规则,如std::string/std::vector等,都是在构造函数中获取资源,在析构函数中自动清除,不需要显式的清除资源。STL中还提供了一些遵守RAII规则的封装器用以管理用户提供的资源。如:

  • std::unique_ptr/std::shared_ptr用于管理动态内存
  • std::lock_guard/std::unique_lock/std::shared_lock用以管理互斥锁

2.RAII型的类和使用

unique_lock为例说明RAII型类的使用。

#include <memory>
#include <iostream>
#include <thread>
#include <functional>
#include <mutex>
#include <chrono>
using namespace std;void increase(std::string name, int n)
{for(auto i=0; i<n; i++) {std::cout << name << ": " << i << std::endl;std::this_thread::sleep_for(200ms);}
}int main()
{int n = 5;auto f1 = std::bind(&increase, "t1", std::placeholders::_1);auto f2 = std::bind(&increase, "t2", std::placeholders::_1);auto t1 = std::make_unique<std::thread>(std::bind(f1, n));auto t2 = std::make_unique<std::thread>(std::bind(f2, n));t1->join();t2->join();
}// t1: 0t2
// : 0
// t2: 1
// t1: 1
// t2: 2
// t1: 2
// t2: 3
// t1: 3
// t2: 4
// t1: 4

上面的代码中,没有做线程同步,两个线程的输出混淆在一起,这不是我们想要的结果。最简单的方式就是使用互斥锁,

void increase(std::string name, int n)
{   mtx.lock();for(auto i=0; i<n; i++) {std::cout << name << ": " << i << std::endl;std::this_thread::sleep_for(200ms);}mtx.unlock();
}
// t1: 0
// t1: 1
// t1: 2
// t1: 3
// t1: 4
// t2: 0
// t2: 1
// t2: 2
// t2: 3
// t2: 4

上面互斥锁的使用,需要手动的设置lockunlock,这不符合RAII的思想,而使用unique_lock/lock_guard,可以在创建lock变量的时候就自动上锁,在lock变量超出作用域,生命周期结束变量销毁时会自动释放锁。其中unique_lock还可以接受defer_lock可以推迟加锁,此时需要手动的上锁和释放锁unique_lock就不符合RAII规则了。

void increase(std::string name, int n)
{   std::unique_lock<std::mutex> ul(mtx);for(auto i=0; i<n; i++) {std::cout << name << ": " << i << std::endl;std::this_thread::sleep_for(200ms);}
}
// t1: 0
// t1: 1
// t1: 2
// t1: 3
// t1: 4
// t2: 0
// t2: 1
// t2: 2
// t2: 3
// t2: 4

在3中,作者基于RAII原则实现了一个互斥锁,其功能和std::lock_guard类似。

其他的诸如管理文件描述符,套接字,登陆信息的上下文等,使用RAII规则时类似。

参考资料

  • 1.https://en.cppreference.com/w/cpp/language/raii
  • 2.https://zhuanlan.zhihu.com/p/91062516
  • 3.https://zhuanlan.zhihu.com/p/34660259