[bt-devel] Programming Techniques
David White
bt-devel@crosswire.org
09 Mar 2002 00:01:20 +1100
Hi everyone,
Martin has asked me to become a BibleTime developer, and I have accepted
his offer. In particular, he wants me to look over the BibleTime code,
and make comment on it's robustness, style, and adherence to good C++
standards, since I have a good deal of experience with C++ and knowledge
about it. I just thought I'd write this email to introduce myself to
everyone on the list, and to offer a few initial suggestions of things
that could be done to improve the BibleTime code base.
A key technique I want to introduce is Resource Acquisition Is
Intialization (RAII). It's a technique invented and popularised by
Bjarne Stroustrup, the creator of C++. BibleTime has alot of code that
looks something like this:
{
Object* ob = new Object();
...do stuff with ob....
delete ob;
}
now, firstly, if possible ob should not be allocated on the heap like
that, it should be allocated on the stack, as in:
{
Object ob;
...do stuff with ob...
}
this is because it is quicker (heap allocations are slow, stack
allocations are fast), easier to read, less code, and there is no hope
of there being a memory leak.
However, sometimes resources do have to be created and then erased
later, for example, opening a file, or creating a dynamic array on the
heap:
{
Object* ob = new Object[x];
...do stuff with ob....
delete [] ob;
}
now, this type of code is very very common, but it has problems.
Firstly, it's difficult to remember to delete ob. Particular if you exit
out of the block early. Secondly, if the stuff using ob throws an
exception, the memory pointed to by ob will be leaked. And, don't tell
me that BibleTime doesn't use exceptions, so this can't happen.
BibleTime uses the new operator, to start with, and the C++ standard
says that if new fails, it will throw an exception. It's just about
impossible to program in C++ without touching exceptions. Even if you
could get around the exception problem, you would still have problem 1,
which is that this is difficult to maintain, because you have to
remember to delete ob.
The RAII technique solves this, it involves using the constructor of a
class to initialize or store a resource, and its constructor to delete
it. So, you would write a resource wrapper something like this:
class Object_array_wrapper {
Object* ob_;
public:
Object_array_wrapper(Object* ob) : ob_(ob) {}
~Object_array_wrapper() { delete [] ob_; }
operator Object*() const { return ob_; }
};
now, with this class you can rewrite the above code like this:
{
Object_array_wrapper ob = new Object[x];
...do stuff with ob exactly as if it was an Object*
} //ob is automatically deleted because the destructor of
//Object_array_wrapper deletes it
this is better, but the problem is you have to write a wrapper class for
every resource type you use. So, I have written a class template, which
allows us to easily generate wrappers for this kind of thing. It is
called scoped_resource, and is found in the header scoped_resource.h.
All you have to do to use it in this situation is this:
{
scoped_array<Object> ob(new Object[x]);
...use ob as if it were an ob*...
} //ob is automatically deleted
easy, no? If you want to delete just a single object, not a whole array,
use scoped_ptr instead of scoped_array. These are inherited from a more
flexible type, called scoped_resource. scoped_resource can manage any
resource type, you just have to tell it how to free the type of the
resource it is managing. e.g. if you want it to automatically close a
file instead of you having to do it:
struct close_file { void operator()(int fd) const { close(fd); } };
{
scoped_resource<int,close_file> file(open("file.txt",O_RDONLY));
...use file...
} //file is automatically closed
I understand this might be all quite confusing, but once you understand
it it's really cool. Please let me know if you don't understand
anything.
One minor point, I have put all this in the namespace util. So you have
to go util::scoped_resource etc. I notice that BibleTime doesn't use
namespaces at the moment, but I suggest we consider changing this.
namespaces are a cool feature that allows much better division of code.
God Bless,
David.