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

Joh*_*ohn 249 c++ raii

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

the*_*ill 337

对于一个非常强大的概念来说,这是一个非常糟糕的名字,也许是C++开发人员在切换到其他语言时所遗漏的第一件事.试图将这个概念重命名为Scope-Bound Resource Management有一点动作,尽管它似乎还没有流行起来.

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

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

与RAII一起

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

ManagedResourceHandle handle(createNewResource());
handle->performInvalidOperation();
Run Code Online (Sandbox Code Playgroud)

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

  • 抛出异常,但您没有捕获它,因此应用程序终止.如果使用try {} catch(){}进行换行,那么它将按预期工作:http://ideone.com/xm2GR9 (7认同)
  • @the_mandrill:我试过 ideone.com/1Jjzuc 这个程序。但是没有析构函数调用。tomdalling.com/blog/software-design/... 说C++ 保证栈上对象的析构函数会被调用,即使抛出异常。那么,为什么析构函数没有在这里执行呢?我的资源是被泄露了还是永远不会被释放或释放? (3认同)
  • 不太确定`Scope-Bound`是否是最佳名称选择,因为存储类说明符_together with_ scope确定实体的存储持续时间.将其缩小到范围限制可能是一种有用的简化,但它并非100%精确 (2认同)
  • 但是原名中的‘RA is初始化’怎么解释呢?我对 RAII 的理解是,一旦超出范围,每个对象都有责任删除它。对我来说,它与“is初始化”不匹配,因为一切都与析构函数相关。我至今仍对这个成语名字感到困惑。 (2认同)
  • “...C++ 开发人员错过的第一件事...”这不是类似于 Java 中的 try-with-resources 吗?它似乎解决了同样的问题,而且与 Java 的解决方案相比,我认为 RAII 没有优点或缺点。 (2认同)

Pét*_*rök 117

这是一个简单的意思是你的编程习语

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

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

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

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

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

  • AFAIK,首字母缩略词并不意味着该对象必须位于本地(堆栈)变量上。它可能是另一个对象的成员变量,因此当“持有”对象被销毁时,成员对象也会被销毁,并且资源被释放。事实上,我认为这个缩写词的具体含义是没有`open()`/`close()`方法来初始化和释放资源,只有构造函数和析构函数,所以资源的“持有”只是生命周期对象的生命周期,无论该生命周期是由上下文(堆栈)还是显式(动态分配)处理 (2认同)

sbi*_*sbi 46

"RAII"代表"资源获取是初始化",实际上是一个误称,因为它不关心资源获取(和对象的初始化),而是释放资源(通过破坏对象) ).
但RAII是我们得到的名字,它坚持.

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

{
  raii obj(acquire_resource());
  // ...
} // obj's dtor will call release_resource()
Run Code Online (Sandbox Code Playgroud)

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

class something {
private:
  raii obj_;  // will live and die with instances of the class
  // ... 
};
Run Code Online (Sandbox Code Playgroud)

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

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

  • `std :: auto_ptr`是`std :: unique_ptr`的过时版本.`std :: auto_ptr`类似于C++ 98中的模拟移动语义,`std :: unique_ptr`使用C++ 11的新移动语义.创建了新类,因为C++ 11的移动语义更明确(除了临时之外需要`std :: move`),而对于来自`std :: auto_ptr`的非const的任何副本,它都是默认的. (4认同)

Den*_*nis 12

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

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

哪里

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

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


elm*_*mar 11

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

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

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

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

来自C ++标准库的使用RAII的示例是std::stringstd::vector

考虑这段代码:

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

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

标准库中的其他例子是使用RAII是std::shared_ptrstd::unique_ptrstd::lock_guard

该技术的另一个名称是SBRM,是范围绑定资源管理的缩写。

  • “SBRM”对我来说更有意义。我提出这个问题是因为我认为我理解 RAII,但这个名字让我感到困惑,听到它被描述为“范围限制资源管理”让我立即意识到我确实理解这个概念。 (3认同)
  • 我不知道为什么没有将此标记为问题的答案。这是一个非常彻底且写得很好的答案,谢谢@elmiomar (2认同)
  • @ConventionalProgrammer 我强调了从程序员显式内存管理到 RAII 类隐式自动内存管理的转变。在手动内存管理中,程序员负责每次分配(新)和释放(删除)调用。通过使用 std::vector、std::unique_ptr、std::shared_ptr 等 RAII 类,内存管理的责任从程序员转移到这些类上。我希望我的观点现在已经清楚了。 (2认同)

Dmi*_*lov 5

自从编译器问世以来,手工内存管理是程序员一直在想办法避免的噩梦。使用垃圾回收器进行编程的语言可以使生活更轻松,但是会降低性能。在本文“ 消除垃圾收集器:RAII方式”中,Toptal工程师Peter Goodspeed-Niklaus向我们介绍了垃圾收集器的历史,并解释了所有权和借用的概念如何在不损害其安全保证的前提下帮助消除垃圾收集器。


小智 5

RAII类分为三个部分:

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

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

  1. 开启档案
  2. 分配一些内存
  3. 取得锁

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

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