Do programmers of other languages, besides C++, use, know or understand RAII?
Asked Answered
S

17

37

I've noticed RAII has been getting lots of attention on Stackoverflow, but in my circles (mostly C++) RAII is so obvious its like asking what's a class or a destructor.

So I'm really curious if that's because I'm surrounded daily, by hard-core C++ programmers, and RAII just isn't that well known in general (including C++), or if all this questioning on Stackoverflow is due to the fact that I'm now in contact with programmers that didn't grow up with C++, and in other languages people just don't use/know about RAII?

Sclerotomy answered 3/10, 2008 at 4:48 Comment(3)
Once again SO proofs it's worth. I usually tend to program this way, but was unaware that it was formalized and called RAII. Thanks.Flection
Do BASIC programmers think of OEG1K (On Error Goto 1000)?Suckling
Other languages sometimes use the execute-around idiom to achieve similar behavior.Jost
T
11

For people who are commenting in this thread about RAII (resource acquisition is initialisation), here's a motivational example.

class StdioFile {
    FILE* file_;
    std::string mode_;

    static FILE* fcheck(FILE* stream) {
        if (!stream)
            throw std::runtime_error("Cannot open file");
        return stream;
    }

    FILE* fdup() const {
        int dupfd(dup(fileno(file_)));
        if (dupfd == -1)
            throw std::runtime_error("Cannot dup file descriptor");
        return fdopen(dupfd, mode_.c_str());
    }

public:
    StdioFile(char const* name, char const* mode)
        : file_(fcheck(fopen(name, mode))), mode_(mode)
    {
    }

    StdioFile(StdioFile const& rhs)
        : file_(fcheck(rhs.fdup())), mode_(rhs.mode_)
    {
    }

    ~StdioFile()
    {
        fclose(file_);
    }

    StdioFile& operator=(StdioFile const& rhs) {
        FILE* dupstr = fcheck(rhs.fdup());
        if (fclose(file_) == EOF) {
            fclose(dupstr); // XXX ignore failed close
            throw std::runtime_error("Cannot close stream");
        }
        file_ = dupstr;
        return *this;
    }

    int
    read(std::vector<char>& buffer)
    {
        int result(fread(&buffer[0], 1, buffer.size(), file_));
        if (ferror(file_))
            throw std::runtime_error(strerror(errno));
        return result;
    }

    int
    write(std::vector<char> const& buffer)
    {
        int result(fwrite(&buffer[0], 1, buffer.size(), file_));
        if (ferror(file_))
            throw std::runtime_error(strerror(errno));
        return result;
    }
};

int
main(int argc, char** argv)
{
    StdioFile file(argv[1], "r");
    std::vector<char> buffer(1024);
    while (int hasRead = file.read(buffer)) {
        // process hasRead bytes, then shift them off the buffer
    }
}

Here, when a StdioFile instance is created, the resource (a file stream, in this case) is acquired; when it's destroyed, the resource is released. There is no try or finally block required; if the reading causes an exception, fclose is called automatically, because it's in the destructor.

The destructor is guaranteed to be called when the function leaves main, whether normally or by exception. In this case, the file stream is cleaned up. The world is safe once again. :-D

Tall answered 3/10, 2008 at 4:58 Comment(8)
Add code to show it being used. To explain why it makes the code exception safe!Brickyard
Okay, here's my first cut at it; let me know what you think. :-)Tall
Not sure why this answer is getting voted up. The question isn't what is RAII, it's more about the standing of the concept amongst non c++ programmers.Bohlen
It's still a good explanation, so I can understand the up-voting. Selecting it as answer is a bit odd though. :)Vogeley
It doesn't particularly help me identify it by what it elegantly handles so you don't need to worry about it. Sure, understand it, but then make it habitual. And it really is primarily about C++.Opposition
The destructor isn't guaranteed to be called after main if there are no try/catch blocks and an exception is thrown, that is implementation defined and MSVC won't call it.Choose
Isn't this basically Python's with statement? Using a c++ object instead of the context.Bushing
@les Python's with is similar to C#'s using: you have to remember to use it, or else you will leak. C++'s RAII is used by default, and you have to do special things to turn it off (e.g., new an object into a raw pointer (which is a big no-no in modern C++), and leak that).Tall
V
26

There are plenty of reasons why RAII isn't better known. First, the name isn't particularly obvious. If I didn't already know what RAII was, I'd certainly never guess it from the name. (Resource acquisition is initialization? What does that have to do with the destructor or cleanup, which is what really characterizes RAII?)

Another is that it doesn't work as well in languages without deterministic cleanup.

In C++, we know exactly when the destructor is called, we know the order in which destructors are called, and we can define them to do anything we like.

In most modern languages, everything is garbage-collected, which makes RAII trickier to implement. There's no reason why it wouldn't be possible to add RAII-extensions to, say, C#, but it's not as obvious as it is in C++. But as others have mentioned, Perl and other languages support RAII despite being garbage collected.

That said, it is still possible to create your own RAII-styled wrapper in C# or other languages. I did it in C# a while ago. I had to write something to ensure that a database connection was closed immediately after use, a task which any C++ programmer would see as an obvious candidate for RAII. Of course we could wrap everything in using-statements whenever we used a db connection, but that's just messy and error-prone.

My solution was to write a helper function which took a delegate as argument, and then when called, opened a database connection, and inside a using-statement, passed it to the delegate function, pseudocode:

T RAIIWrapper<T>(Func<DbConnection, T> f){
  using (var db = new DbConnection()){
    return f(db);
  }
}

Still not as nice or obvious as C++-RAII, but it achieved roughly the same thing. Whenever we need a DbConnection, we have to call this helper function which guarantees that it'll be closed afterwards.

Vogeley answered 28/12, 2008 at 16:47 Comment(1)
I am a .Net developer by background so can you clarify more on why 'using' will still cause issue (messy and error-prone)? Is it the resources in the 'using' scope is not instantly dispose after leaving the scope but instead is waiting to be garbage collected? If that is the case in what cases will that be error prone?Caddy
T
21

I use C++ RAII all the time, but I've also developed in Visual Basic 6 for a long time, and RAII has always been a widely-used concept there (although I've never heard anyone call it that).

In fact, many VB6 programs rely on RAII quite heavily. One of the more curious uses that I've repeatedly seen is the following small class:

' WaitCursor.cls '
Private m_OldCursor As MousePointerConstants

Public Sub Class_Inititialize()
    m_OldCursor = Screen.MousePointer
    Screen.MousePointer = vbHourGlass
End Sub

Public Sub Class_Terminate()
    Screen.MousePointer = m_OldCursor
End Sub

Usage:

Public Sub MyButton_Click()
    Dim WC As New WaitCursor

    ' … Time-consuming operation. '
End Sub

Once the time-consuming operation terminates, the original cursor gets restored automatically.

Twoup answered 3/10, 2008 at 11:44 Comment(0)
S
14

RAII stands for Resource Acquisition Is Initialization. This is not language-agnostic at all. This mantra is here because C++ works the way it works. In C++ an object is not constructed until its constructor completes. A destructor will not be invoked if the object has not been successfully constructed.

Translated to practical language, a constructor should make sure it covers for the case it can't complete its job thoroughly. If, for example, an exception occurs during construction then the constructor must handle it gracefully, because the destructor will not be there to help. This is usually done by covering for the exceptions within the constructor or by forwarding this hassle to other objects. For example:

class OhMy {
public:
    OhMy() { p_ = new int[42];  jump(); } 
    ~OhMy() { delete[] p_; }

private:
    int* p_;

    void jump();
};

If the jump() call in the constructor throws we're in trouble, because p_ will leak. We can fix this like this:

class Few {
public:
    Few() : v_(42) { jump(); } 
    ~Few();

private:
    std::vector<int> v_;

    void jump();
};

If people are not aware of this then it's because of one of two things:

  • They don't know C++ well. In this case they should open TCPPPL again before they write their next class. Specifically, section 14.4.1 in the third edition of the book talks about this technique.
  • They don't know C++ at all. That's fine. This idiom is very C++y. Either learn C++ or forget all about this and carry on with your lives. Preferably learn C++. ;)
Spurge answered 3/10, 2008 at 5:10 Comment(0)
T
11

For people who are commenting in this thread about RAII (resource acquisition is initialisation), here's a motivational example.

class StdioFile {
    FILE* file_;
    std::string mode_;

    static FILE* fcheck(FILE* stream) {
        if (!stream)
            throw std::runtime_error("Cannot open file");
        return stream;
    }

    FILE* fdup() const {
        int dupfd(dup(fileno(file_)));
        if (dupfd == -1)
            throw std::runtime_error("Cannot dup file descriptor");
        return fdopen(dupfd, mode_.c_str());
    }

public:
    StdioFile(char const* name, char const* mode)
        : file_(fcheck(fopen(name, mode))), mode_(mode)
    {
    }

    StdioFile(StdioFile const& rhs)
        : file_(fcheck(rhs.fdup())), mode_(rhs.mode_)
    {
    }

    ~StdioFile()
    {
        fclose(file_);
    }

    StdioFile& operator=(StdioFile const& rhs) {
        FILE* dupstr = fcheck(rhs.fdup());
        if (fclose(file_) == EOF) {
            fclose(dupstr); // XXX ignore failed close
            throw std::runtime_error("Cannot close stream");
        }
        file_ = dupstr;
        return *this;
    }

    int
    read(std::vector<char>& buffer)
    {
        int result(fread(&buffer[0], 1, buffer.size(), file_));
        if (ferror(file_))
            throw std::runtime_error(strerror(errno));
        return result;
    }

    int
    write(std::vector<char> const& buffer)
    {
        int result(fwrite(&buffer[0], 1, buffer.size(), file_));
        if (ferror(file_))
            throw std::runtime_error(strerror(errno));
        return result;
    }
};

int
main(int argc, char** argv)
{
    StdioFile file(argv[1], "r");
    std::vector<char> buffer(1024);
    while (int hasRead = file.read(buffer)) {
        // process hasRead bytes, then shift them off the buffer
    }
}

Here, when a StdioFile instance is created, the resource (a file stream, in this case) is acquired; when it's destroyed, the resource is released. There is no try or finally block required; if the reading causes an exception, fclose is called automatically, because it's in the destructor.

The destructor is guaranteed to be called when the function leaves main, whether normally or by exception. In this case, the file stream is cleaned up. The world is safe once again. :-D

Tall answered 3/10, 2008 at 4:58 Comment(8)
Add code to show it being used. To explain why it makes the code exception safe!Brickyard
Okay, here's my first cut at it; let me know what you think. :-)Tall
Not sure why this answer is getting voted up. The question isn't what is RAII, it's more about the standing of the concept amongst non c++ programmers.Bohlen
It's still a good explanation, so I can understand the up-voting. Selecting it as answer is a bit odd though. :)Vogeley
It doesn't particularly help me identify it by what it elegantly handles so you don't need to worry about it. Sure, understand it, but then make it habitual. And it really is primarily about C++.Opposition
The destructor isn't guaranteed to be called after main if there are no try/catch blocks and an exception is thrown, that is implementation defined and MSVC won't call it.Choose
Isn't this basically Python's with statement? Using a c++ object instead of the context.Bushing
@les Python's with is similar to C#'s using: you have to remember to use it, or else you will leak. C++'s RAII is used by default, and you have to do special things to turn it off (e.g., new an object into a raw pointer (which is a big no-no in modern C++), and leak that).Tall
B
9

RAII.

It starts with a constructor and destructor but it is more than that.
It is all about safely controlling resources in the presence of exceptions.

What makes RAII superior to finally and such mechanisms is that it makes code safer to use because it moves responsibility for using an object correctly from the user of the object to the designer of the object.

Read this

Example to use StdioFile correctly using RAII.

void someFunc()
{
    StdioFile    file("Plop","r");

    // use file
}
// File closed automatically even if this function exits via an exception.

To get the same functionality with finally.

void someFunc()
{
      // Assuming Java Like syntax;
    StdioFile     file = new StdioFile("Plop","r");
    try
    {
       // use file
    }
    finally
    {
       // close file.
       file.close(); // 
       // Using the finaliser is not enough as we can not garantee when
       // it will be called.
    }
}

Because you have to explicitly add the try{} finally{} block this makes this method of coding more error prone (i.e. it is the user of the object that needs to think about exceptions). By using RAII exception safety has to be coded once when the object is implemented.

To the question is this C++ specific.
Short Answer: No.

Longer Answer:
It requires Constructors/Destructors/Exceptions and objects that have a defined lifetime.

Well technically it does not need exceptions. It just becomes much more useful when exceptions could potentially be used as it makes controlling the resource in the presence of exceptions very easy.
But it is useful in all situations where control can leave a function early and not execute all the code (e.g. early return from a function. This is why multiple return points in C is a bad code smell while multiple return points in C++ is not a code smell [because we can clean up using RAII]).

In C++ controlled lifetime is achieved by stack variables or smart pointers. But this is not the only time we can have a tightly controlled lifespan. For example Perl objects are not stack based but have a very controlled lifespan because of reference counting.

Brickyard answered 3/10, 2008 at 5:16 Comment(0)
B
8

The problem with RAII is the acronym. It has no obvious correlation to the concept. What does this have to do with stack allocation? That is what it comes down to. C++ gives you the ability to allocate objects on the stack and guarantee that their destructors are called when the stack is unwound. In light of that, does RAII sound like a meaningful way of encapsulating that? No. I never heard of RAII until I came here a few weeks ago, and I even had to laugh hard when I read someone had posted that they would never hire a C++ programmer who'd didn't know what RAII was. Surely the concept is well known to most all competent professional C++ developers. It's just that the acronym is poorly conceived.

Bohlen answered 28/12, 2008 at 16:7 Comment(0)
T
5

A modification of @Pierre's answer:

In Python:

with open("foo.txt", "w") as f:
    f.write("abc")

f.close() is called automatically whether an exception were raised or not.

In general it can be done using contextlib.closing, from the documenation:

closing(thing): return a context manager that closes thing upon completion of the block. This is basically equivalent to:

from contextlib import contextmanager

@contextmanager
def closing(thing):
    try:
        yield thing
    finally:
        thing.close()

And lets you write code like this:

from __future__ import with_statement # required for python version < 2.6
from contextlib import closing
import urllib

with closing(urllib.urlopen('http://www.python.org')) as page:
    for line in page:
        print line

without needing to explicitly close page. Even if an error occurs, page.close() will be called when the with block is exited.

Titoism answered 3/10, 2008 at 17:54 Comment(0)
D
3

Common Lisp has RAII:

(with-open-file (stream "file.ext" :direction :input)
    (do-something-with-stream stream))

See: http://www.psg.com/~dlamkins/sl/chapter09.html

Druce answered 27/2, 2009 at 20:36 Comment(0)
S
2

First of all I'm very surprised it's not more well known! I totally thought RAII was, at least, obvious to C++ programmers. However now I guess I can understand why people actually ask about it. I'm surrounded, and my self must be, C++ freaks...

So my secret.. I guess that would be, that I used to read Meyers, Sutter [EDIT:] and Andrei all the time years ago until I just grokked it.

Sclerotomy answered 3/10, 2008 at 5:2 Comment(4)
I think its that a lot of people know the concept, but dont know the terminology.Anni
That could be, they probably don't relate the two.Sclerotomy
That's exactly how I got to knew about RAII. Thanks to Meyers, Sutters & Andrei!Kinsella
Having learned C++ years ago, the acronym meant nothing to me. It's like asking about "the LBW technique when walking". LBW? What's that? Look Both Ways. Well, of course I do that when I cross the street. Why didn't you say that in the first place. And what's the point of asking the question?Cocoon
M
1

The thing with RAII is that it requires deterministic finalization something that is guaranteed for stackbased objects in C++. Languages like C# and Java that relies on garbage collection doesn't have this guarantee so it has to be "bolted" on somehow. In C# this is done by implementing IDisposable and much of the same usage patterns then crops up basicly that's one of the motivators for the "using" statement, it ensures Disposal and is very well known and used.

So basicly the idiom is there, it just doesn't have a fancy name.

Macron answered 3/10, 2008 at 5:34 Comment(4)
NOTE: 'using' only addresses function locals -- or rather, objects with lifetime constrained entirely to one stack frame. it doesn't address class statics, function statics, or class members, or heap allocated objects which have lifetime scoped in other ways.Substance
IDispose != RAII. You, the user of an object, have to put those using statements everywhere to get the same effect, and even then, it doesn't work if an object has other objects embedded in it. IDispose is syntactic sugar for calling 'close' on every object, not RAII.Anthropoid
c# does allow you to alloc on the stack, however I think you lost the type safety guarantee since you're essentially working with raw memory and all bets are off in the managed world when you do this.Bohlen
The idiom is not there in languages like C# - please use C++ for a while and you will see what you just said was hogwashFighter
P
1

RAII is a way in C++ to make sure a cleanup procedure is executed after a block of code regardless of what happens in the code: the code executes till the end properly or raises an exception. An already cited example is automatically closing a file after its processing, see answer here.

In other languages you use other mechanism to achieve that.

In Java you have try { } finally {} constructs:

try {
  BufferedReader file = new BufferedReader(new FileReader("infilename"));
  // do something with file
}
finally {
    file.close();
}

In Ruby you have the automatic block argument:

File.open("foo.txt") do | file |
  # do something with file
end

In Lisp you have unwind-protect and the predefined with-XXX

(with-open-file (file "foo.txt")
  ;; do something with file
)

In Scheme you have dynamic-wind and the predefined with-XXXXX:

(with-input-from-file "foo.txt"
  (lambda ()
    ;; do something 
)

in Python you have try finally

try
  file = open("foo.txt")
  # do something with file
finally:
  file.close()

The C++ solution as RAII is rather clumsy in that it forces you to create one class for all kinds of cleanup you have to do. This may forces you to write a lot of small silly classes.

Other examples of RAII are:

  • unlocking a mutex after acquisition
  • closing a database connection after opening
  • freeing memory after allocation
  • logging on entry and exit of a block of code
  • ...
Presumably answered 3/10, 2008 at 7:24 Comment(6)
The C++ solution is better, because code of cleaning is written once (either in the destructor, or using a smart pointer), and the order of destruction will be respected automatically, unlike the clumsy patterns shown in your examples.Tyronetyrosinase
And I won't even talk about the Garbage collector of some languages (i.e. C#) executing the same time, in another thread, than your finalizer, which leads to the funny conclusion of another question in SO: Don't use RAII to disposed managed resources in C#.Tyronetyrosinase
In Python you can use a with statement. See my answer.Titoism
Using "with" in Python or "using" in C# is inferior - it puts the burden on the user of the class to use these keywords. With destructors, the burden is on the class writer and it needs to be done only once.Toon
@Nemanja Trifunovic: It seems you don't understand how with statement works in Python. You write a context manager only once. Compare with Lock(obj) as l:... in Python and { Lock l(obj); ...} in C++ e.g., both could call obj. acquire() on entering the block and obj.release() on the exit.Titoism
It's not necessary to make a new class for each separate cleanup activity -- you can use Alexei Alexandrescu's ScopeGuard (google it) to ensure that any function you care to provide is called on scope exit.Coreen
F
0

It's sort of tied to knowing when your destructor will be called though right? So it's not entirely language-agnostic, given that that's not a given in many GC'd languages.

Fia answered 3/10, 2008 at 4:52 Comment(1)
Surer it is language agnostic. Many idioms and patterns are not available or are hard to imlement in various languages because of features they lack.Fighter
T
0

I think a lot of other languages (ones that don't have delete, for example) don't give the programmer quite the same control over object lifetimes, and so there must be other means to provide for deterministic disposal of resources. In C#, for example, using using with IDisposable is common.

Tall answered 3/10, 2008 at 4:54 Comment(0)
P
0

RAII is popular in C++ because it's one of the few (only?) languages that can allocate complex scope-local variables, but does not have a finally clause. C#, Java, Python, Ruby all have finally or an equivalent. C hasn't finally, but also can't execute code when a variable drops out of scope.

Phoenician answered 3/10, 2008 at 4:58 Comment(7)
C++ does need finally because of RAII (deliberate decision). These other languages need finally because they don't have RAII. finally is not a good control mechanism it is a band aid added to resolve problems.Brickyard
Martin: Hear, hear! (s/does/doesn't/, I presume?)Tall
Sorry. Dyslexia and fast typing are not a good mix. C++ does NOT need finally.Brickyard
RAII comes with its own set of problems, like massively complicated destructors and an explosion of classes for handling various kinds of resource acquisitions.Phoenician
@John: Yes there is a cost. But it is pushing the responcability from the user of the object to the designer of the object.Brickyard
@Martin: I agrree, the "massively complicated destrcutors" only duplicate the code that would have to be cut-and-pasted into who knows how many finally blocks. RAII reduces the potential for errorFighter
@John: You don't need new classes if you use ScopeGuard, though you do currently need to write a function for each type of deallocation. If that function happens to already exist (e.g. fclose()) then you don't even need that. Things will be even better when we get lambda expressions in C++1x...Coreen
E
0

I have colleagues who are hard-core, "read the spec" C++ types. Many of them know RAII but I have never really heard it used outside of that scene.

Epiphytotic answered 11/10, 2008 at 18:0 Comment(0)
M
-1

CPython (the official Python written in C) supports RAII because of its use of reference counted objects with immediate scope based destruction (rather than when garbage is collected). Unfortunately, Jython (Python in Java) and PyPy do not support this very useful RAII idiom and it breaks a lot of legacy Python code. So for portable python you have to handle all the exceptions manually just like Java.

Micheal answered 11/10, 2008 at 17:39 Comment(2)
the way memory handling works in C extension of python has nothing to do with RAII, and is certainly not scope based. I wish it were, though, because as much as I hate C++, RAII is the one thing that makes C++ better than C in this area.Rigsby
I used to think that Python with its ref counting scheme supports RAII... But it doesn't. Ok, there are some situations where it does work, but it gets complex very fast especially when the interpreter is exiting and modules have been unloaded.Singsong
O
-2

RAII is specific to C++. C++ has the requisite combination of stack-allocated objects, unmanaged object lifetimes, and exception handling.

Ogdoad answered 3/10, 2008 at 4:58 Comment(1)
Incorrect. It needs controlled lifespan. This is not limited to stack objects. EG. Perl can do RAII and it uses ref-counted objects.Brickyard

© 2022 - 2024 — McMap. All rights reserved.