c++ - 理解术语和概念的含义——RAII(Resource Acquisition is Initialization)

标签 c++ garbage-collection raii resource-management

各位 C++ 开发人员能否给我们一个关于什么是 RAII 的很好的描述,为什么它很重要,以及它是否可能与其他语言有任何相关性?

我知道一点。我相信它代表“资源获取即初始化”。但是,该名称与我对 RAII 是什么(可能不正确)的理解不一致:我的印象是 RAII 是一种在堆栈上初始化对象的方法,这样,当这些变量超出范围时,析构函数将自动被调用导致资源被清理。

那么为什么不叫“使用堆栈触发清理”(UTSTTC:)?你如何从那里到“RAII”?

你怎么能在堆栈上做一些东西来清理堆上的东西?另外,是否存在无法使用 RAII 的情况?你有没有发现自己希望垃圾收集?至少有一个垃圾收集器可以用于某些对象同时让其他对象受到管理?

谢谢。

最佳答案

So why isn't that called "using the stack to trigger cleanup" (UTSTTC:)?



RAII 告诉您该做什么:在构造函数中获取您的资源!我想补充一点:一种资源,一种构造函数。 UTSTTC 只是其中的一种应用,RAII 则更多。

资源管理很烂。 在这里,资源是使用后需要清理的任何东西。跨多个平台的项目研究表明,大多数错误与资源管理有关 - 在 Windows 上尤其糟糕(由于对象和分配器的多种类型)。

在 C++ 中,由于异常和(C++ 风格)模板的组合,资源管理特别复杂。如需了解引擎盖下的一瞥,请参阅 GOTW8 )。

C++ 保证析构函数被调用 当且仅当 构造函数成功了。依靠这一点,RAII 可以解决许多普通程序员甚至可能不知道的棘手问题。这里有一些超出“每当我返回时我的局部变量将被销毁”的例子。

让我们从一个过于简单的 FileHandle 开始使用 RAII 的类:
class FileHandle
{
    FILE* file;

public:

    explicit FileHandle(const char* name)
    {
        file = fopen(name);
        if (!file)
        {
            throw "MAYDAY! MAYDAY";
        }
    }

    ~FileHandle()
    {
        // The only reason we are checking the file pointer for validity
        // is because it might have been moved (see below).
        // It is NOT needed to check against a failed constructor,
        // because the destructor is NEVER executed when the constructor fails!
        if (file)
        {
            fclose(file);
        }
    }

    // The following technicalities can be skipped on the first read.
    // They are not crucial to understanding the basic idea of RAII.
    // However, if you plan to implement your own RAII classes,
    // it is absolutely essential that you read on :)



    // It does not make sense to copy a file handle,
    // hence we disallow the otherwise implicitly generated copy operations.

    FileHandle(const FileHandle&) = delete;
    FileHandle& operator=(const FileHandle&) = delete;



    // The following operations enable transfer of ownership
    // and require compiler support for rvalue references, a C++0x feature.
    // Essentially, a resource is "moved" from one object to another.

    FileHandle(FileHandle&& that)
    {
        file = that.file;
        that.file = 0;
    }

    FileHandle& operator=(FileHandle&& that)
    {
        file = that.file;
        that.file = 0;
        return *this;
    }
}

如果构造失败(有异常(exception)),则不会调用其他成员函数 - 甚至析构函数 - 不会被调用。

RAII 避免使用处于无效状态的对象。 在我们使用对象之前,它已经让生活变得更轻松。

现在,让我们看看临时对象:
void CopyFileData(FileHandle source, FileHandle dest);

void Foo()
{
    CopyFileData(FileHandle("C:\\source"), FileHandle("C:\\dest"));
}

三种错误情况需要处理:无法打开文件、只能打开一个文件、两个文件都可以打开但复制文件失败。在非 RAII 实现中,Foo必须明确处理所有三种情况。

RAII 会释放已获取的资源,即使在一个语句中获取了多个资源。

现在,让我们聚合一些对象:
class Logger
{
    FileHandle original, duplex;   // this logger can write to two files at once!

public:

    Logger(const char* filename1, const char* filename2)
    : original(filename1), duplex(filename2)
    {
        if (!filewrite_duplex(original, duplex, "New Session"))
            throw "Ugh damn!";
    }
}
Logger的构造函数如果 original 会失败的构造函数失败(因为 filename1 无法打开),duplex的构造函数失败(因为 filename2 无法打开),或写入 Logger 中的文件的构造函数体失败。在任何这些情况下,Logger的析构函数不会被调用 - 所以我们不能依赖 Logger的析构函数来释放文件。但如果 original被构造后,它的析构函数将在 Logger 的清理过程中被调用。构造函数。

RAII 简化了部分构建后的清理工作。

负分:

负分?所有问题都可以用 RAII 和智能指针解决 ;-)

当您需要延迟获取,将聚合对象推送到堆上时,RAII 有时很笨拙。
想象一下 Logger 需要一个 SetTargetFile(const char* target) .在那种情况下,句柄仍然需要是 Logger 的成员。 , 需要驻留在堆上(例如在智能指针中,以适本地触发句柄的销毁。)

我从来没有真正希望垃圾收集。当我使用 C# 时,我有时会感到片刻的幸福,我只是不需要在意,但更多的是我想念所有可以通过确定性破坏创建的很酷的玩具。 (使用 IDisposable 只是不会削减它。)

我有一个特别复杂的结构,它可能从 GC 中受益,其中“简单”的智能指针会导致对多个类的循环引用。我们通过仔细平衡强指针和弱指针来蒙混过关,但无论何时我们想要改变某些东西,我们都必须研究一个大的关系图。 GC 可能会更好,但一些组件持有应该尽快释放的资源。

关于 FileHandle 示例的说明:它并不打算完整,只是一个示例 - 但结果不正确。感谢 Johannes Schaub 指出并感谢 FredOverflow 将其转化为正确的 C++0x 解决方案。随着时间的推移,我已经接受了这种方法 documented here .

关于c++ - 理解术语和概念的含义——RAII(Resource Acquisition is Initialization),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/712639/

相关文章:

git - git在执行操作时会做什么:git gc-git prune

cocoa - 清理 Finalize 和 dealloc 中的变量

java - ~1s 延迟控制应用程序 : is this suitable for Java?

c++ - 在单个(成员)函数的范围之外应用 RAII

java - java中如何知道一个对象有多少个引用

c++ - 资源获取是初始化 "RAII"

c++ - 为什么当我使用不带括号的函数时 C++ 编译器不报错?

c++ - Assimp 面孔指数

c++ - 无法识别的命令行选项 “-std=c++11”

c++ - Qt mac 版本缺少框架