logoalt Hacker News

jesse__today at 1:18 AM3 repliesview on HN

Another commentor succinctly pointed out one argument against RAII+friends is that it encourages thinking about single objects, as opposed to bulk processing.

In many contexts, the common case is in fact bulk processing, and programming things with the assumption that everything is a single, discrete element creates several problems, mostly wrt. performance, but also maintainability. [1][2]

> The whole point of an arena allocator is the exact opposite of RAII

Yes, agreed. And the internet is rife with people yelling about just how great RAII is, but comparatively few people have written specifically about it's failings, and alternatives, which is what I'm asking about today.

[1] https://www.youtube.com/watch?v=tD5NrevFtbU

[2] https://www.youtube.com/watch?v=rX0ItVEVjHc&t=2252


Replies

vacuitytoday at 2:13 AM

I don't think the "complex web of objects to be deallocated" scenario is usually a problem, but I generally agree with your points. As always, careful design and control of the software is important. Abstractions are limited; spend them carefully.

HarHarVeryFunnytoday at 2:22 PM

The alternative to RAII is simply do-it-yourself ! For example, if you are writing multi-threaded code and need a mutex to protect some data structure, then you'd need an explicit mutex_lock() before the access, and an explicit mutex_unlock() afterwards... if your code might throw exceptions or branch due to errors, then make sure that you have mutex_unlock() calls everywhere necessary!

Automating this paired lock and unlock, to avoid any programmer error in missing an unlock in one of the error paths, just makes more sense, and this is all that RAII is doing - it's not some mysterious religion or design philosophy that needs to pervade your program (other than to extent that it would make sense to remove all such potential programmer errors, not just some!).

In this mutex example, RAII would just mean having a class "MutexLocker" (C++ provides std::lock_guard) that does the mutex_lock() in it's constructor and mutex_unlock() in it's destructor.

Without RAII, your code might have looked something like this:

try {

  mutex_lock(mut);

  // access data structure

  mutex_unlock(mut);
} catch (...) { // make sure we unlock the mutex in the error case too !

  mutex_unlock(mut);
}

With the RAII approach the mutex unlocks are automatic since they'll happen as soon as the mutex locker goes out of scope and its destructor is called, so the equivalent code would now look like this:

try {

  MutexLocker locker(mut);

  // access data structure
} catch (...) {

}

That's it - no big deal, but note that now there was no need to add multiple mutex unlock calls in every (normal + error) exit path, and no chance of forgetting to do it.

You can do the same thing anywhere where you want to guarantee that some paired "resource" cleanup activity take place, whether that is unlocking a mutex, or closing a file, or releasing a memory buffer, or whatever.

You may not think of it as RAII, but this approach of automatic cleanup is being used anywhere you have classes that own something then release it when they are done with it, for example things like std::string (and all the C++ container classes) is doing this, as are C++ <stream> file objects, C++ smart pointers, C++ lock_guard for mutexes, etc, etc.

The name "RAII" (resource acquisition is initialization) is IMO a poor name for the technique, since the value is more in the guaranteed, paired, resource release than the acquisition, which was never a problem. Scope-based resource management, or Lifetime-based resource management, would better describe it!

Of course RAII isn't always directly applicable because maybe you need to acquire a resource in one place and release it someplace else entirely, not in same scope, although you could choose to use a smart pointer together with a RAII resource manager to achieve that. For example create a resource with std::make_shared<ResourceManager>() in one place, and resource release will still happen automatically whenever reference counting indicates that all uses of the resource are done.

HarHarVeryFunnytoday at 3:13 AM

> one argument against RAII+friends is that it encourages thinking about single objects, as opposed to bulk processing.

RAII is just a way to guarantee correctness by tying a resource's lifetime to that of an object, with resource release guaranteed to happen. It is literally just saying that you will manage your resource (a completely abstract concept - doesn't have to be memory) by initializing it in an objects constructor and release it in the destructor.

Use of RAII as a technique is completely orthogonal to what your program is doing. Maybe you have a use for it in a few places, maybe you don't. It's got nothing to do with whether you are doing "bulk procesing" or not, and everything to do with whether you have resources whose usage you want to align to the lifetime of an object (e.g this function will use this file/mutex/buffer, then must release it before it exits).