何时不使用 RAII 进行资源管理 [关闭]

Posted

技术标签:

【中文标题】何时不使用 RAII 进行资源管理 [关闭]【英文标题】:When not to use RAII for resource management [closed] 【发布时间】:2011-03-18 21:36:38 【问题描述】:

谁能提供一个或多个具体示例,其中 RAII 不是最有效的资源管理方法,为什么?

【问题讨论】:

直到现在还没有找到。很高兴得到启发。 【参考方案1】:

我能想到 RAII 不是解决方案的唯一情况是多线程关键区域锁管理。通常,建议获取临界区锁(考虑 资源)并将其保存在 RAII 对象中:

void push( Element e ) 
   lock l(queue_mutex);  // acquire on constructing, release on destructing
   queue.push(e);

但在某些情况下,您不能为此目的使用 RAII。特别是,如果循环条件中使用的变量被多个线程共享,并且您无法为整个循环执行持有锁,那么您必须使用不同的机制来获取和释放锁:

void stop_thread() 
   lock l(control_mutex);
   exit = true;

void run() 
   control_mutex.acquire();
   while ( !exit )  // exit is a boolean modified somewhere else
      control_mutex.release();
      // do work
      control_mutex.acquire();
   
   control_mutex.release();

现在我想到了(ab)使用operator, 甚至可以使用RAII,但我从未真正想到过。但我想这不是很自然:

void run() 
   while ( lock(control_mutex), !exit ) 
      // do work
   

所以我想答案不是我能想象的……

编辑:使用 RAII 解决同一问题的其他解决方案:

@Mark Ransom:

bool should_exit() const 
   lock l(mutex);
   return exit;

void run() 
   while ( !should_exit() ) 
      // do work
   

@fnieto:

void run() 
   while (true) 
        lock l(mutex);
         if (exit) break;
      
      // do work
   

【讨论】:

在有人问之前,operator, 的使用是由 5.18[expr.comma]/1 保证的:“左表达式的所有副作用 (1.9),除了破坏temporaries (12.2),在评估正确的表达式之前执行。" 与其直接在循环中查询标志,你不能​​把它放在一个函数中,将标志访问包装在RAII锁内吗? @Mark:对,你可以。还有一位同事 (@fnieto) 提出了一种不同的方法:while (true) lock l(mutex); if (exit) break; ... 再次使用 RAII,并且比operator, 使用更容易阅读。这与您的建议非常相似,因为它们将检查移到循环条件之外,以便可以将其包含在自己的范围内。【参考方案2】:

有时需要两阶段初始化(创建、初始化、使用)。

甚至是三阶段:在我们的产品中,有一组独立对象,每个对象都运行一个线程,并且能够通过优先级继承队列订阅任意数量的其他对象(包括它自己)。启动时从配置文件中读取对象及其订阅。在构造时,每个对象 RAII 是它可以获取的所有内容(文件、套接字等),但没有对象可以订阅其他对象,因为它们是以未知的顺序构造的。因此,在构建完所有对象之后,第二阶段是建立所有连接,第三阶段是在建立所有连接后,释放线程并开始消息传递。同样,关闭也是多阶段的。

【讨论】:

我的直接反应是每个初始化程度本身都可能是一种资源。抽象该资源的对象除了在构造和销毁时调用引用对象上的方法之外几乎没有什么作用。但是,“可能”与“好主意”不同。多阶段初始化和清理(一个简单的有限状态模型)是解决某些问题的好方法。【参考方案3】:

GC 可以为程序员处理循环数据结构的内存,而 RAII 则需要程序员在某处手动中断循环。

【讨论】:

你能举个例子吗? 识别哪些内存对象是垃圾相对容易,与周期无关。在存在循环的情况下确定有效的销毁顺序是困难的。 GC 语言通过 not 解决这个问题来解决这个问题 - 它们声明终结器不能保证运行,因此必须手动处理涉及内存以外的资源的清理,这意味着您有相同的生命周期管理问题作为 GC 应该修复的形式。不过,这是一个很好的答案 - 如果唯一不平凡的管理资源是内存,那么 GC 比 RAII 更好,这并不少见。 确实,在您的对象图中手动管理非内存资源或手动管理循环之间是有关系的。恕我直言,通常不清楚哪个是最好的,因为通常难以管理稀缺资源和循环数据结构都不太常见。 还要注意RAII和循环问题是不相关的问题。循环问题与引用计数有关,这只是可能的 RAII 策略之一。您可以使用前向 shared_ptr 和后向 weak_ptr 保存一个双链表,并且您都将使用 RAII 并且不会遇到循环问题。 正如@David 所说,RAII 不仅仅是参考计数。共享指针被荒谬地过度使用。它们不应该是默认选择。【参考方案4】:

RAII 意味着资源的所有权是通过语言构造提供的保证来定义和管理的,最明显的是但不限于构造函数和析构函数。

C++ 中 RAII 的意义在于资源所有权政策实际上可以由语言强制执行。 RAII 的一个较小的替代方案是 API 建议调用者(例如,通过 cmets 或其他文档)在特定时间显式执行 ACQUIRE()RELEASE() 操作。这种政策不能由语言强制执行。

因此,最初的问题是另一种方式来询问是否存在无法执行的资源管理方法比 RAII 更可取的情况。 我能想到的唯一情况是您故意规避该语言中现有的资源管理结构,并编写自己的框架。例如,您正在实现一个垃圾收集脚本语言解释器。原子的“虚拟分配”可能会与内存块玩游戏。同样,基于池的分配器希望程序最终调用DESTROY_POOL() 操作,并产生全局后果(即,从该池分配的任何项目都将失效)。

【讨论】:

【参考方案5】:

在资源释放可能失败的情况下,RAII 可能不足以管理该资源(因为析构函数不应该抛出)。不过,RAII 可能仍然是该解决方案的一部分。

【讨论】:

以上是关于何时不使用 RAII 进行资源管理 [关闭]的主要内容,如果未能解决你的问题,请参考以下文章

Java中的RAII ...资源处理总是那么难看吗?

C++基于RAII对锁进行封装

PHP 是不是支持 RAII 模式?如何?

C++11的资源管理:泛化的RAII

RAII&智能指针

C++RAII(Resource Acquisition Is Initialization 资源获取即初始化)是什么?(raii)