7
$\begingroup$

RAII, the acronym for "resource acquisition is initialization", is a crucial paradigm of C++ that is meant to prevent the following things:

  • Forgetting to free a dynamic storage
  • Forgetting to close a file
  • Forgetting to unlock a mutex

Though C++ fails to completely prevent these caveats for many reasons, I'm coming up with a way for perfectly obeying RAII. The idea is to basically to render the objects in question as primitive types, and to offer syntaxes and semantics for ensuring safety. (Namely, the said syntaxes and semantics I've come up with are the notion of scoped variable declaration, but its explicit demonstration is out of the scope of this question.)

But are there any RAII-requiring notions whose safety issues are parallel to the three listed situations above?

$\endgroup$
5
  • 18
    $\begingroup$ As Kate Gregory said, RAII is a bad name for what should be called "scope-based resource management" $\endgroup$
    – Jonathan
    Commented Jun 24 at 10:18
  • 4
    $\begingroup$ @Jonathan: I think it's more that RAII is a very C++ specific term for scope-based resource management. Within C++, it's perfectly sensible, outside of that context not so much. $\endgroup$ Commented Jun 24 at 13:03
  • 1
    $\begingroup$ So are you reinventing Java's try-with-resource? $\endgroup$
    – feldentm
    Commented Jun 24 at 15:10
  • 13
    $\begingroup$ There are no situations that require RAII, and there is an unbounded list of situations where it might be a good idea. $\endgroup$
    – OrangeDog
    Commented Jun 24 at 15:30
  • 3
    $\begingroup$ Resource management constrained to a scope inside a function, with RAII or with using or similar, works. Things get complicated when resources need to escape function scope. That's one thing Rust ownership mechanics tries to (help to) solve, and you can see it's not trivial. $\endgroup$
    – Pablo H
    Commented Jun 25 at 13:22

5 Answers 5

20
$\begingroup$

There is no complete list, because many of the most "interesting" examples are application-specific.

Consider something like transaction safety in a database server: a transaction must either 100% happen or 100% not happen, even if an exception is thrown. Committing or rolling back a transaction in progress is often handled by RAII in database servers written in C++.

The three examples that you gave are usually irrelevant if the program is exiting; memory and file handles will all be cleaned up by the operating system, so doing it manually may be wasting the user's time. Indeed, flushing I/O buffers are probably more important than closing file handles.

But application-specific "resources", such as persistent transaction safety, are usually not.

$\endgroup$
1
  • 1
    $\begingroup$ Also closing communication channels may involve sending exit messages. You are only right about the OS collecting them wrt. the underlying resources, not the protocols. $\endgroup$
    – feldentm
    Commented Jun 24 at 15:13
16
$\begingroup$

You should basically never write code that requires you to write corresponding code later in the control flow.

The obvious examples are resource acquisition things like allocating memory (you have to free it later) or opening a file (you have to close it later), and there are lots of other examples provided around this question.

The problem is that when you do this, you make a hidden rule that, in order to maintain your code correctly, whenever you change the code in one place, you have to change the code in the other place in a corresponding way. In fact, all the code that might affect the opened/closed, allocated/free or whatever status needs to be maintained as a single unit, even though it could be spread out across the code base.

That is a recipe for bugs, and these bugs used to happen all the time.

Now, these kinds of bugs hardly ever happen, because professional C++ programmers always use use RAII to make sure there are no hidden correspondences like that.

If you acquire a resource or do something else that needs to be cleaned up later, then the code that does that should also ensure that it gets cleaned up automatically, at the appropriate time, as variables that refer to that resource go out of scope.

This is a very general rule. It doesn't make sense to ask for an exhaustive list of all the things that require RAII, because the list is endless, and programmers need to be able to create their own.

I use RAII when generating XML, for example, to ensure that if I write a start tag, then I will also write the corresponding end tag at some point. That sort of thing is not going to be on anybody's pre-written list of "RAII situations".

$\endgroup$
7
  • 3
    $\begingroup$ The "corresponding code" phrase is a nice generalization. One can even omit the "later in the control flow": Iterators, for example, obviate the need to adapt loops to array lengths defined elsewhere. Polymorphism lets one extend class hierarchies without adapting code elsewhere. -- Suddenly RAII appears as a form of eliminating redundancy. $\endgroup$ Commented Jun 26 at 0:24
  • $\begingroup$ > Iterators, for example, obviate the need to adapt loops to array lengths defined elsewhere. Most code in C that I've seen just uses a constant for the array size, so you never really need to update the loops $\endgroup$ Commented Jun 26 at 3:34
  • 2
    $\begingroup$ @CoffeeTableEspresso a constant is still code $\endgroup$
    – Caleth
    Commented Jun 26 at 8:25
  • $\begingroup$ @Caleth my point is it's code in one single place, not an update to every single loop. So it's not really in the same category as a lot of the other examples (imo). $\endgroup$ Commented Jun 26 at 13:14
  • $\begingroup$ @CoffeeTableEspresso the name of the constant is in at least two places: the array declaration and the loop. One of those places can be changed to a different name without the other. And if most of the C you have seen uses constants for array sizes, you haven't seen much C. Loads of C I have seen has dynamic arrays $\endgroup$
    – Caleth
    Commented Jun 26 at 14:58
13
$\begingroup$

There are no things that require RAII to exist, (as evidenced by various languages not having it). RAII helps in avoiding resource management bugs but is not required to solve the resource management problem as a whole.

Though having some way to tie a cleanup action to a scope is very useful. This is the defer statement as found in Go and Odin and D's (scope(exit)), the finally clause in Java, or the with statement in python or the try-with-resources in java. 2 out of the 3 actions you listed can be solved with something like that rather than a RAII local.

Adding RAII to a language brings in some complications, you now need to account for 4 ways of moving an object; from a source that can/cannot be clobbered (copy vs. move) to a destination that is not yet/already "alive", (assign vs. construct).

$\endgroup$
2
  • 4
    $\begingroup$ Do note that defer/scope(exit)/finally are pretty good for scope bound variables, but not as good when the resources are transferred, because they're tied to the variable, not the resource itself. $\endgroup$ Commented Jun 24 at 11:28
  • $\begingroup$ But then I dearly missed RAII in C# (as well as copy semantics, which may indicate I was operating under the wrong paradigm). $\endgroup$ Commented Jun 26 at 0:19
3
$\begingroup$

The general notion of resources should be better thought of as a responsibility. An entity acquires a responsibility when it asks some other entity to change its behavior, to the detriment of some other entities, until further notice; after having issued the request, the entity that issues it has a responsibility to ensure that the appropriate notice will be given.

A memory manager may be asked "please refrain from allowing anyone else to use this memory until I say I'm done with it". A file manager may be asked "please refrain from allowing anyone else to use this file, and to the extent possible defer updating the version on disk, until I say I'm done with it". A database may be asked "please refrain from allowing anyone to access record X until I say I'm done with it.".

Instead of trying to think in low-level terms about files, memory, and so on, it's far better to recognize the high level concept of responsibility to deliver notice, since that underlies almost all "resource cleanup" patterns.

BTW, a feature that is all too often missing in RAII-style constructs is a means of distinguishing normal and abnormal circumstances for giving notice. If code which is editing a database record throws an exception, having the responsibility wrapper notify the database that the transaction has been abandoned would be useful, but if the controlled block exits without having either committed or rolled back the transaction, it would often be more useful to throw an "improperly abandoned transaction" exception. The abandonment of the transaction shouldn't cause a double-exception fault, but it should throw an exception in situations that would otherwise represent "normal" release.

$\endgroup$
0
$\begingroup$
  • Forgetting to unregister an event in the C# style of event system.

But if your language has weakrefs, you automatically solve this before calling it RAII. If it doesn't, but depends on other garbage collectors, because this reference exists, the event callback target is never freed, so it can never unregister automatically. If it doesn't, and objects could be unmanaged, you may have other memory safety problems.

  • Forgetting to revert changes in backtracking algorithms.

For example, in the internal implementation of regex, one should not expose information in unmatched paths. Depending on how you implement this, it may cover all side effects and extend the scope of this problem by too much.

$\endgroup$
16
  • 1
    $\begingroup$ Weakrefs generally don't solve the same problem as RAII, because with GC you usually have no guarantee of promptness. RAII and similar mechanisms have well-defined cleanup time, whereas an orphan weakref may linger indefinitely until the GC finalises it. Memory pressure is different from resource pressure, and a GC triggers only on the former. Critically, in many runtimes GC finalisation may never run if memory is not exhausted or the program shuts down. $\endgroup$ Commented Jun 26 at 10:28
  • 1
    $\begingroup$ @Caleth you don't have guarantees of WHEN the clean-up code runs, which is a critical component of RAII. $\endgroup$ Commented Jun 26 at 13:16
  • 1
    $\begingroup$ @Caleth I disagree with you that it handles the problem. Say I'm listening on a specific port. In many cases I'd like to be able to immediately reuse that port, but I can't if it may take an arbitrary amount of time until that port is available again. RAII solves this for me, weakrefs do not. $\endgroup$ Commented Jun 26 at 14:43
  • 1
    $\begingroup$ @Caleth I'm also not fully understanding your second point. I use RAII all the time, I've never had to worry about inheriting from Object, and it's never been particularly complicated as you seem to be making it out to be $\endgroup$ Commented Jun 26 at 14:44
  • 1
    $\begingroup$ @Caleth You only hold onto whatever you "registered with", be it a resource representation, an event loop, an event handler, or whatever. You don't hold onto everything that listens to anything. At least, in any sane programming language or resource framework. Holding onto everything is what leads to leaks! $\endgroup$ Commented Jun 26 at 15:10

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .