资源获取的意思是初始化(RAII)?

时间:2010-02-23 20:35:58

标签: c++ raii

资源获取的含义是初始化(RAII)?

9 个答案:

答案 0 :(得分:322)

对于一个非常强大的概念来说,这是一个非常可怕的名称,也许是C ++开发人员在切换到其他语言时所遗漏的第一件事。尝试将此概念重命名为范围限制资源管理,已经有一些动作,尽管它似乎还没有流行起来。

当我们说'资源'时,我们不仅仅意味着内存 - 它可能是文件句柄,网络套接字,数据库句柄,GDI对象......简而言之,我们有一个有限的供应的东西,所以我们需要能够控制他们的使用。 “范围限制”方面意味着对象的生命周期绑定到变量的范围,因此当变量超出范围时,析构函数将释放资源。一个非常有用的特性是它可以提高安全性。例如,比较一下:

RawResourceHandle* handle=createNewResource();
handle->performInvalidOperation();  // Oops, throws exception
...
deleteResource(handle); // oh dear, never gets called so the resource leaks

使用RAII一个

class ManagedResourceHandle {
public:
   ManagedResourceHandle(RawResourceHandle* rawHandle_) : rawHandle(rawHandle_) {};
   ~ManagedResourceHandle() {delete rawHandle; }
   ... // omitted operator*, etc
private:
   RawResourceHandle* rawHandle;
};

ManagedResourceHandle handle(createNewResource());
handle->performInvalidOperation();

在后一种情况下,当抛出异常并且堆栈被展开时,局部变量被破坏,这确保我们的资源被清理并且不会泄漏。

答案 1 :(得分:109)

这是一种编程习惯用语,简单地表示你

  • 将资源封装到一个类中(其构造函数通常 - 但不一定是** - 获取资源,其析构函数总是释放它)
  • 通过类*
  • 的本地实例使用资源
  • 当对象超出范围时,资源会自动释放

这保证了在资源正在使用时发生的任何事情,它最终都会被释放(无论是由于正常返回,包含对象的破坏还是抛出异常)。

它是C ++中广泛使用的良好实践,因为除了是一种处理资源的安全方式之外,它还使您的代码更加清晰,因为您不需要将错误处理代码与主要功能混合使用。

* 更新:“local”可能表示本地变量或类的非静态成员变量。在后一种情况下,成员变量使用其所有者对象进行初始化和销毁​​。

** Update2:正如@sbi指出的那样,资源 - 虽然经常在构造函数中分配 - 也可以在外部分配并作为参数传入。

答案 2 :(得分:44)

“RAII”代表“资源获取是初始化”,实际上是一个用词不当,因为它不是资源获取(以及对象的初始化),但是< em>释放资源(通过对象的销毁) 但RAII是我们得到的名字,它坚持。

这个成语的核心功能是在 本地,自动对象 中封装资源(内存块,打开文件,未锁定的互斥锁,你的名字),当对象在其所属范围的末尾被销毁时,让该对象的析构函数释放资源:

{
  raii obj(acquire_resource());
  // ...
} // obj's dtor will call release_resource()

当然,对象并不总是本地的自动对象。他们也可以成为一个班级的成员:

class something {
private:
  raii obj_;  // will live and die with instances of the class
  // ... 
};

如果这些对象管理内存,它们通常被称为“智能指针”。

这有很多变化。例如,在第一个代码段中,问题出现了如果有人想要复制obj会发生什么。最简单的方法是简单地禁止复制。 std::unique_ptr<>是一个智能指针,是下一个C ++标准所特有的标准库的一部分 另一个这样的智能指针std::shared_ptr具有它所拥有的资源(动态分配的对象)的“共享所有权”。也就是说,它可以自由复制,所有副本都可以引用同一个对象。智能指针跟踪多少个副本引用同一个对象,并在最后一个副本被销毁时将其删除 第三个变体以std::auto_ptr为特色,它实现了一种移动语义:一个对象只由一个指针拥有,并且试图复制一个对象将导致(通过语法hackery)将对象的所有权转移到复制操作的目标。

答案 3 :(得分:11)

本书C++ Programming with Design Patterns Revealed将RAII描述为:

  1. 获取所有资源
  2. 使用资源
  3. 释放资源
  4. 其中

    • 资源是作为类实现的,所有指针都有类包装器(使它们成为智能指针)。

    • 通过调用其构造函数获取资源,并通过调用其析构函数隐式释放(以获取的相反顺序)。

答案 4 :(得分:6)

对象的生存期由其范围决定。但是,有时我们需要创建一个对象,该对象与创建对象的作用域无关,这是有用的,或者很有用。在C ++中,运算符 new 用于创建这样的对象。要破坏对象,可以使用运算符 delete 。由操作员 new 创建的对象是动态分配的,即在动态内存(也称为 heap free store )中分配。因此,由 new 创建的对象将继续存在,直到使用 delete 将其明确销毁为止。

使用 new delete 时可能发生的一些错误是:

  • 泄漏的对象(或内存):使用 new 分配对象,而忘记使用 delete 对象。
  • 过早删除(或悬挂参考):持有指向对象的另一个指针, delete ,然后使用另一个指针。
  • 双重删除:尝试两次两次 delete

通常,范围变量是首选。但是,RAII可以用作 new delete 的替代方法,以使对象独立于其范围而存在。这种技术包括将指针分配到在堆上分配的对象,然后将其放在 handle / manager对象中。后者具有一个析构函数,将负责销毁该对象。这样可以确保该对象可用于任何想要访问它的函数,并且可以在 handle对象的生命周期结束时销毁该对象,而无需进行显式清理。

使用RAII的C ++标准库示例为 std::string std::vector

考虑这段代码:

void fn(const std::string& str)
{
    std::vector<char> vec;
    for (auto c : str)
        vec.push_back(c);
    // do something
}

当创建向量并将元素推入向量时,您不必担心分配和取消分配此类元素。向量使用 new 为其堆上的元素分配空间,并使用 delete 释放该空间。作为vector的用户,您无需关心实现细节,并且会相信vector不会泄漏。在这种情况下,向量是其元素的 handle对象

使用RAII的标准库中的其他示例为 std::shared_ptr std::unique_ptr std::lock_guard

此技术的另一个名称是 SBRM ,是 Scope-Bound Resource Management 的缩写。

答案 5 :(得分:4)

手动内存管理是一个噩梦,程序员一直在发明自编译器发明以来避免的方法。使用垃圾收集器编程语言可以使生活更轻松,但代价是性能。在本文中 - Eliminating the Garbage Collector: The RAII Way,Toptal工程师Peter Goodspeed-Niklaus向我们展示了垃圾收集器的历史,并解释了所有权和借用的概念如何帮助消除垃圾收集器而不损害其安全保障。

答案 6 :(得分:4)

RAII类分为三个部分:

  1. 在析构函数中放弃了资源
  2. 该类的实例是堆栈分配的
  3. 在构造函数中获取资源。这部分是 可选,但很常见。

RAII代表“资源获取是初始化”。 RAII的“资源获取”部分是您开始某些必须在以后结束的事情的地方,例如:

  1. 打开文件
  2. 分配一些内存
  3. 获取锁

“正在初始化”部分意味着获取发生在类的构造函数内部。

https://www.tomdalling.com/blog/software-design/resource-acquisition-is-initialisation-raii-explained/

答案 7 :(得分:1)

RAII概念只是C堆栈变量的想法。最简单的解释方式。

答案 8 :(得分:1)

许多人认为RAI​​I是用词不当,但实际上它是该惯用语的正确名称,只是解释得不好。

Wikipedia详细解释了行为: 资源获取即初始化(RAII)是一种编程习惯用法,在几种面向对象的静态类型的编程语言中用于描述特定的语言行为。在RAII中,持有资源是类不变的,并且与对象生存期相关:资源分配(或获取)是在构造函数的对象创建(特别是初始化)期间完成的,而资源释放(释放)则在对象破坏期间进行(具体来说是最终确定)。换句话说,资源获取必须成功才能使初始化成功。因此,可以保证资源在初始化完成和完成之间进行保留(保持资源是类不变的),并且仅在对象处于活动状态时才保留。因此,如果没有对象泄漏,就不会有资源泄漏。

现在,对于名称来说,它仅表示“资源获取”操作是初始化操作,并且应该是资源类对象的初始化/构造函数的一部分。换句话说,使用这种惯用法,使用资源意味着在构造类对象时使资源类保存该资源并初始化资源。这暗示着资源的释放应该在资源类析构函数中对称发生。

您当然可以选择不使用该习语,但是如果您想知道使用该习语会得到什么,那就是

RAII 对于更大的C ++项目,通常不包含构造函数/析构函数对之外的对new或delete(或malloc / free)的单个调用是很常见的。或根本没有。