When not to use RAII for resource management [closed]
Asked Answered
S

5

6

Can anyone provide me with one or more concrete examples in which RAII was not the most efficient method for resource management, and why?

Sliver answered 21/7, 2010 at 9:48 Comment(1)
Never found one till now. Would be happy to be enlightened.Revile
S
4

The only case I can think of where RAII was not the solution is with multithreaded critical region lock management. In general it is advisable to acquire the critical region lock (consider that the resource) and hold it in a RAII object:

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

But there are situations where you cannot use RAII for that purpose. In particular, if a variable used in a loop condition is shared by multiple threads, and you cannot hold the lock for the whole loop execution, then you must acquire and release the lock with a different mechanism:

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();
}

It might even be possible to use RAII by (ab)using operator, now that I think of, but I had never actually thought of it. But I guess this is not really natural:

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

So I guess that the answer is that not that I can imagine...

EDIT: Other solutions for the same problem using 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
   }
}
Sectionalism answered 21/7, 2010 at 17:38 Comment(3)
Before someone asks, that usage of operator, with a temporary is guaranteed by 5.18[expr.comma]/1: "All side effects (1.9) of the left expression, except for the destruction of temporaries (12.2), are performed before the evaluation of the right expression."Flyspeck
Rather than querying the flag directly in the loop, couldn't you put it in a function that wraps the flag access within the RAII lock?Dipietro
@Mark: right, you can. Also a coworker (@fnieto) suggested a different approach: while (true) { { lock l(mutex); if (exit) break; } ... } that again uses RAII and is easier to read than the operator, use. This is quite similar to your suggestion in that they move the check outside of the loop condition so that it can be enclosed in its own scope.Flyspeck
A
1

Sometimes two-stage initialization (create, then init, then use) is needed.

Or even three-stage: in our product, there is a collection of independent objects, each running a thread and able to subscribe to any number of other objects (including itself) via priority-inheriting queues. Objects and their subscriptions are read from the config file at startup. At construction time, each object RAIIs everything it can (files, sockets, etc), but no object can subscribe to others because they are constructed in unknown order. So then after all objects are constructed there's the second stage where all connections are made, and third stage when, once all connections are made, the threads are let go and begin messaging. Likewise, shutdown is mutli-stage as well.

Anarthrous answered 21/7, 2010 at 10:19 Comment(1)
My immediate reaction here is that each degree of initialisation could be a resource in itself. An object to abstract that resource might do little other than call methods on a referenced object on construction and destruction. "Possible" isn't the same as "good idea", though. Multi-stage initialisation and cleanup (a simple finite state model) is a good approach for some problems.Traveller
I
0

GC can handle the memory of cyclic data structures for the programmer while RAII will require the programmer to manually break the cycle somewhere.

Inflect answered 21/7, 2010 at 9:54 Comment(6)
Can you please give an example?Sliver
Identifying which in-memory objects are garbage is relatively easy, irrespective of cycles. Determining a valid destruction order in the presence of cycles is hard. GC languages solve this problem by not solving it - they declare finalizers to be not guaranteed to run, so cleanups involving resources other than memory must be handled manually, meaning you have a problem of the same lifetime-managing form as the one that GC is supposed to fix. This is a good answer, though - if the only non-trivial-to-manage resource is memory, GC is better than RAII, which isn't that uncommon.Traveller
indeed, the coice is between manually managing non-memory resources or manually managing cycles in your object graph. imho its usually not clear which is best because usually both hard to manage scarce resources and cyclic data structures are less common.Inflect
Also note that RAII and the cycle problem are unrelated issues. The cycle problem is related to reference counting, that is just one of the possible RAII strategies. You can hold a double linked list with forward shared_ptr and backward weak_ptr and you will both be using RAII and not having issues with the cycles.Flyspeck
As @David said, RAII is much more than ref counting. Shared pointers are ridiculously overused. They should not be the default choice.Othaothe
That was kind of the point. there is no one size fits all solution so there is some manual analysis + chocie of smart pointers to use.Inflect
R
0

RAII means that the ownership of resources is defined and managed through the guarantees provided by the language constructs, most notably, but not limited to, constructors and destructors.

The point of RAII in C++ is that the resource ownership policy can actually be enforced by the language. A lesser alternative to RAII is for the API to advise the caller (e.g., through comments or other documentation) to explicitly perform ACQUIRE() and RELEASE() operations at certain times. That kind of policy is not enforceable by the language.

So the original question is another way to ask whether there are cases when an unenforceable approach to resource management is preferable to RAII. The only cases I can think of are where you are deliberately circumventing the existing resource management constructs in the language, and writing your own framework. For example, you are implementing a garbage collected scripting language interpreter. The "virtual allocation" of atoms will likely play games with memory blocks. Similarly, a pool based allocator expects the program to eventually call a DESTROY_POOL() operation, with global consequences (i.e., any item allocated from that pool will be invalidated).

Runner answered 21/7, 2010 at 14:39 Comment(0)
R
0

In cases where resource release may fail, RAII may not be sufficient to manage that resource (since destructors shouldn't throw). RAII may still be part of that solution though.

Rubious answered 21/7, 2010 at 18:7 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.