RAII vs finally
C++ does not have a finally
construct and I believe this is a good thing.
Bjarne Stroustrup explains that “in realistic systems, there
are far more resource acquisitions than kinds of resources, so the RAII
technique leads to less code than use of a finally
construct”. This article
compares C++ RAII to finally
constructs in other languages.
Introduction (examples in Java)
Java has a finally
construct. It’s supposed to be used like this:
1
2
3
4
5
6
7
8
9
public void foo() {
try {
// code
// that might throw
} finally {
// code to cleanup
// regardless of wether an exception is thrown
}
}
In particular if we create an object we would often like to call a method (say
close
) in a finally block.
1
2
3
4
5
6
7
8
9
10
public void foo() {
SomeClass obj = new SomeClass();
try {
// code
// that might also call something like
// obj.bar()
} finally {
obj.close();
}
}
This is such a common pattern that Java has a special syntax for it that is
called try-with-resources. As long as the object implements the AutoClosable
interface (which has a close
method) the code bellow will be equivalent to
the one above.
1
2
3
4
5
6
7
public void foo() {
try (SomeClass obj = new SomeClass()) {
// code
// that might also call something like
// obj.bar()
}
}
Other languages
C# has almost the same syntax for finally
as Java:
- Java
finally
: C#finally
- Java try-with-resources: C#
using
- Java
AutoClosable
: C#IDisposable
Python has finally
and it has with
as the equivalent of try-with-resources.
Ruby has a ensure
keyword. The equivalent of try-with-resources are some
functions with blocks e.g.:
1
2
3
4
5
6
7
8
9
10
11
12
13
File.open('src.txt', 'r') do |f|
puts f.readlines
end
# which is logically implemented as:
def open(file_name, mode)
f = actually_open(file_name, mode)
begin
yield(f)
ensure
f.close
end
end
Issues
The first issue is that every time you use a class that wraps some
resource, like SomeClass
you need to use the syntax above (either try-finally
or try-with-resources)
RAII wins on this one with a more compact syntax on usage.
What Bjarne Stroustrup means by “in realistic systems, there are far more
resource acquisitions than kinds of resources” is that you’ll use classes that
wrap resources more than the number of classes. E.g. even in a trivial
example that copies a file from a source to a destination, the file
class
that needs to close a file is used twice.
The second issue is that in garbage collected languages usually memory is still only released when garbage collected. In C++ memory is a resource in the same way as other resources.
The third issue is that if you not use a try-finally or try-with-resource the resource will be released only later, when garbage collected. That leads to subtle bugs like unable to access files for a while, or temporarily running out of database connections, until the garbage collector collects them.
C++ is deterministic, in that the resources get released when the object goes out of scope on the stack or gets deleted from the heap. This is entirely under the control of the programmer, not delayed.
The fourth issue is composition. If a class has a member variable that wraps a
resource, also has to implement AutoClosable
and has to be used with special
syntax.
Bad examples using finally
There is large amount of bad examples, even in official documentation.
For example in this C# example below if the buffer
allocation fails the file is not closed. Also the check for null
at line 10
is not required because if the StreamReader
constructor fails we don’t reach
that code.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
string path = @"c:\users\public\test.txt";
System.IO.StreamReader file = new System.IO.StreamReader(path);
char[] buffer = new char[10];
try
{
file.ReadBlock(buffer, index, buffer.Length);
}
finally
{
if (file != null)
{
file.Close();
}
}
// Do something with buffer...
In Java as well, there is an abundance of confusing examples even in offical documentation that probably work most of the time, but require effort on usage/reading to ensure correctness.
1
2
3
4
5
6
7
8
9
10
11
12
13
public void writeList() {
PrintWriter out = null;
try {
out = new PrintWriter(new FileWriter("OutFile.txt"));
// code
// that might also call something like
// out.println()
} finally {
if (out != null) {
out.close();
}
}
}
In the code above, when would out
be null
? When either FileWriter
or
PrintWriter
constructors fail. If FileWriter
succeeds, but PrintWriter
fails, then we’re in trouble because we fail to close the file before we exit
the function. However that constructor of PrintWriter
is very unlikely to
fail.
Sumary
C++ RAII is a better option than finally
in realistic systems because it has
a simpler syntax from the resource class user’s point of view.