RAII,全称资源获取即初始化(英語:Resource Acquisition Is Initialization),它是在一些面向对象语言中的一种慣用法英语Programming idiom。RAII源于C++,在JavaC#DAdaValaRust中也有应用。1984-1989年期间,比雅尼·斯特勞斯特魯普安德鲁·柯尼希英语Andrew Koenig (programmer)在设计C++异常时,为解决資源管理英语Resource management (computing)时的異常安全英语Exception safety性而使用了该用法[1],后来比雅尼·斯特勞斯特魯普将其称为RAII[2]

RAII要求,资源的有效期与持有资源的对象生命周期严格绑定,即由对象的构造函数完成资源的分配英语Resource allocation (computer)(获取),同时由析构函数完成资源的释放。在这种要求下,只要对象能正确地析构,就不会出现资源泄漏问题。

作用

编辑

RAII的主要作用是在不失代码简洁性[3]的同时,可以很好地保证代码的异常安全性。

下面的C++实例说明了如何用RAII访问文件和互斥量:

#include <string>
#include <mutex>
#include <iostream>
#include <fstream>
#include <stdexcept>
 
void write_to_file(const std::string & message)
{
    // 建立文件的互斥鎖
    static std::mutex mutex;
 
    // 訪問文件前加鎖
    std::lock_guard<std::mutex> lock(mutex);
 
    // 試著打開文件
    std::ofstream file("example.txt");
    if (!file.is_open())
        throw std::runtime_error("unable to open file");
 
    // 輸出內容
    file << message << std::endl;
 
    // 當離開時,文件會被解構(不管是否有異常)
    // 互斥鎖也會被解構(不管是否有異常)
}

C++保证了所有栈对象在生命周期结束时会被销毁(即调用析构函数)[4],所以该代码是异常安全的。无论在write_to_file函数正常返回时,还是在途中抛出异常时,都会引发write_to_file函数的堆栈回退,而此时会自动调用lock和file对象的析构函数。

当一个函数需要通过多个局部变量来管理资源时,RAII就显得非常好用。因为只有被构造成功(构造函数没有抛出异常)的对象才会在返回时调用析构函数[4],同时析构函数的调用顺序恰好是它们构造顺序的反序[5],这样既可以保证多个资源(对象)的正确释放,又能满足多个资源之间的依赖关系。

由于RAII可以极大地简化资源管理,并有效地保证程序的正确和代码的简洁,所以通常会强烈建议在C++中使用它。

典型用法

编辑

RAII在C++中的应用非常广泛,如C++标准库中的lock_guard[6]便是用RAII方式来控制互斥量:

template <class Mutex> class lock_guard {
private:
    Mutex& mutex_;

public:
    lock_guard(Mutex& mutex) : mutex_(mutex) { mutex_.lock(); }
    ~lock_guard() { mutex_.unlock(); }

    lock_guard(lock_guard const&) = delete;
    lock_guard& operator=(lock_guard const&) = delete;
};

程序员可以非常方便地使用lock_guard,而不用担心异常安全问题

extern void unsafe_code();  // 可能抛出异常

using std::mutex;
using std::lock_guard;

mutex g_mutex;

void access_critical_section()
{
    lock_guard<mutex> lock(g_mutex);
    unsafe_code();
}

实际上,C++标准库的实现就广泛应用了RAII,典型的如容器智能指针等。

RRID

编辑

RAII还有另外一种被称为RRID(Resource Release Is Destruction)的特殊用法[7],即在构造时没有“获取”资源,但在析构时释放资源。ScopeGuard[8]和Boost.ScopeExit[9]就是RRID的典型应用:

#include <functional>

class ScopeGuard {
private:
    typedef std::function<void()> destructor_type;

    destructor_type destructor_;
    bool dismissed_;

public:
    ScopeGuard(destructor_type destructor) : destructor_(destructor), dismissed_(false) {}

    ~ScopeGuard()
    {
        if (!dismissed_) {
            destructor_();
        }
    }

    void dismiss() { dismissed_ = true; }

    ScopeGuard(ScopeGuard const&) = delete;
    ScopeGuard& operator=(ScopeGuard const&) = delete;
};

ScopeGuard通常用于省去一些不必要的RAII封装,例如

void foo()
{
    auto fp = fopen("/path/to/file", "w");
    ScopeGuard fp_guard([&fp]() { fclose(fp); });

    write_to_file(fp);                     // 异常安全
}

D语言中,scope关键字也是典型的RRID用法,例如

void access_critical_section()
{
    Mutex m = new Mutex;
    lock(m); 
    scope(exit) unlock(m);

    unsafe_code();                  // 异常安全
}

Resource create()
{
    Resource r = new Resource();
    scope(failure) close(f);

    preprocess(r);                  // 抛出异常时会自动调用close(r)
    return r;
}

與finally的比較

编辑

虽然RAII和finally都能保证资源管理时的异常安全,但相对来说,使用RAII的代码相对更加简洁。 如比雅尼·斯特劳斯特鲁普所说,“在真实环境中,调用资源释放代码的次数远多于资源类型的个数,所以相对于使用finally来说,使用RAII能减少代码量。”[10]

例如在Java中使用finally来管理Socket资源

void foo() {
    Socket socket;
    try {
        socket = new Socket();
        access(socket);
    } finally {
        socket.close();
    }
}

在采用RAII后,代码可以简化为

void foo() {
    try (Socket socket = new Socket()) {
        access(socket);
    }
}

特别是当大量使用Socket时,重复的finally就显得没有必要。

参考资料

编辑
  • Stroustrup, Bjarne. The C++ Programming Language [C++程序设计语言]. Addison-Wesley. 2000 [2014-09-29]. ISBN 0-201-70073-5. (原始内容存档于2014-06-30) (英语). 
  • Stroustrup, Bjarne. The Design and Evolution of C++ [C++语言的设计和演化]. Addison-Wesley. 1994 [2014-09-29]. ISBN 0-201-54330-3. (原始内容存档于2023-06-06) (英语). 
  • Wilson, Matthew. ,Imperfect C++ [Imperfect C++中文版]. Addison-Wesley. 2004 [2023-11-10]. ISBN 0321228774. (原始内容存档于2023-06-05) (英语). 

📚 Artikel Terkait di Wikipedia

Berkeley套接字

Network Programming 正體中文版(zh-tw)(页面存档备份,存于互联网档案馆) - 2014 Beej's Guide to Network Programming 简体中文版(zh-cn)(页面存档备份,存于互联网档案馆) - 2014 UnixSocket FAQ(页面存档备份,存于互联网档案馆)

Ryzen

Ridge」四CPU核心+GPU的晶片。 主流效能級平台使用μPGA封裝,Socket AM4插座。極致效能型號採用與Epyc相同的LGA封裝,與Socket SP3相同規格的Socket TR4。 僅支援DDR4記憶體,支援DDR4-2666單面(Rank)記憶體模組規格或DD

原始套接字

在计算机网络中, 原始套接字(raw socket)是一种网络套接字,允许直接发送/接收IP协议数据包而不需要任何传输层协议格式。 对于标准的套接字,通常数据按照选定的传输层协议(例如TCP、UDP)自动封装,socket用户并不知道在网络介质上广播的数据包含了这种协议包头。

理查德·史蒂文斯

Gilligan, R. E., Thomson, S., Bound, J., and Stevens, W. R. 1999年. "Basic Socket Interface Extensions for IPv6," RFC 2553 Allman, M., Paxson, V., Stevens

Tcl

mode fconfigure $sock -blocking no -buffering line # call handleData when socket is readable fileevent $sock readable [ list handleData $sock ] } proc handleData

岱鐠科技有限公司

規模擴展,公司業務提供自動化燒錄設備(Automated IC Programming System)以及晶片代工燒錄服務(IC Programming Service),並為半導體客戶客製化設計晶片測試座(IC Test Socket)。 公司成立於 2005 年,總部位於台北,台灣,在亞洲和美國設有跨國辦事處。

Bun

Bun内置了对FFI(英语:foreign function interface)、SQLite3、TLS 1.3和DNS的支持。它还提供了文件编辑、HTTP服务器、WebSocket和哈希等API。 Bun 1.0发布于2023年9月8日。Bun最初的版本仅支持MacOS,在0.0.28版本开始支持Linux,自1.1版本起支持Microsoft

奔腾 (初代)

万个晶体管,面积为 140平方毫米 。该过程具有四个互连级别。 虽然 P55C 仍然与Socket 7兼容,但为芯片供电的电压要求与标准的 Socket 7 规格不同。在 P55C 标准建立之前为 Socket 7 制造的大多数主板不符合该 CPU 正常运行所需的双电压轨(2.8 伏核心电压,3.3