N
Nick Keighley
Hi,
In the beginning the code was:-
BRUSHH new_brush = create_brush()
BRUSHH old_brush = select_brush (new_brush)
draw(...)
select_brush (old_brush)
destroy_brush (new_brush)
Each function call is a wrapper around a C API call (don't worry
this in't a question about the API). Any of these wrappers can
throw. If it matters a new brush is created, then selected, the
draw() call uses the brush, then everything is restored how it
was. Obviously this code leaks resources like a sieve. But I
didn't care if an error occured- it was reported and the program
terminated. The OS could clean up the mess.
But then the draw() call threw an error I wanted to recover
from. This obviously would not do (just for starters new_brush
would be lost). So I invoked RAII and wrote this:-
Brush_RM new_brush = create_brush()
// operator* returns a BRUSHH
BRUSHH old_brush = select_brush (*new_brush)
draw(...)
select_brush (old_brush)
// ~Brush_RM() calls destroy_brush(new_brush)
Brush_RM is a resource manager class- a smart pointer [1].
Obviously this is not much better (slightly worse?). If it throws
in the draw() a destroyed brush will be used in the later code.
So:-
Brush_RM new_brush = create_brush() // throw on error
Select_brush old_brush = select_brush (*new_brush) // throw on
// error
draw(...)
// ~Select_brush() calls select_brush(old_brush)
// ~Brush_RM() calls destroy_brush(new_brush)
I havn't dealt with c-ctors or assignment (obviously I will have to).
The functions called in the dtors must not throw. Since the
original C-APIs don't throw this isn't hard. The ctors of the new
classes *must* throw (or Bad Things will happen).
So the code looks nice and neat and I havn't got any try-catch
clauses. But I've introduced two new (but simple) classes. Have I
avoided leaks? Am I making life too difficult? Just so you have a fair
chance to shoot me down these are the classes:-
class Brush_RM
{
public:
Brush_RM(BRUSHH br): brush_(br) {}
~Brush_RM() { destroy_brush(brush_); // don't throw }
BRUSHH operator*() { return brush_; }
private:
BRUSHH brush_;
};
class Select_brush
{
public:
Select_brush(BRUSHH br): brush_(br) {}
~Select_brush() { select_brush(brush_); // don't throw }
private:
BRUSHH brush_;
}
I could continue fiddling with this. Heavens, I could actually try
compiling it! But I figure I might learn something by just posting what
I've got.
[1] names: am I alone? I've never liked the term "auto_ptr()".
Yeah right it's automatic- but what does it *do*? I'm probably way
too late to be saying this... How come Boost have about four
different sorts of "auto_ptr"?
--
Nick Keighley
A ruby trembled. Two tourmaline nets failed to rectify the laser beam.
A diamond noted the error. Both the error and the correction went into
the general computer.
Corwainer Smith "The Dead Lady of Clown Town"
In the beginning the code was:-
BRUSHH new_brush = create_brush()
BRUSHH old_brush = select_brush (new_brush)
draw(...)
select_brush (old_brush)
destroy_brush (new_brush)
Each function call is a wrapper around a C API call (don't worry
this in't a question about the API). Any of these wrappers can
throw. If it matters a new brush is created, then selected, the
draw() call uses the brush, then everything is restored how it
was. Obviously this code leaks resources like a sieve. But I
didn't care if an error occured- it was reported and the program
terminated. The OS could clean up the mess.
But then the draw() call threw an error I wanted to recover
from. This obviously would not do (just for starters new_brush
would be lost). So I invoked RAII and wrote this:-
Brush_RM new_brush = create_brush()
// operator* returns a BRUSHH
BRUSHH old_brush = select_brush (*new_brush)
draw(...)
select_brush (old_brush)
// ~Brush_RM() calls destroy_brush(new_brush)
Brush_RM is a resource manager class- a smart pointer [1].
Obviously this is not much better (slightly worse?). If it throws
in the draw() a destroyed brush will be used in the later code.
So:-
Brush_RM new_brush = create_brush() // throw on error
Select_brush old_brush = select_brush (*new_brush) // throw on
// error
draw(...)
// ~Select_brush() calls select_brush(old_brush)
// ~Brush_RM() calls destroy_brush(new_brush)
I havn't dealt with c-ctors or assignment (obviously I will have to).
The functions called in the dtors must not throw. Since the
original C-APIs don't throw this isn't hard. The ctors of the new
classes *must* throw (or Bad Things will happen).
So the code looks nice and neat and I havn't got any try-catch
clauses. But I've introduced two new (but simple) classes. Have I
avoided leaks? Am I making life too difficult? Just so you have a fair
chance to shoot me down these are the classes:-
class Brush_RM
{
public:
Brush_RM(BRUSHH br): brush_(br) {}
~Brush_RM() { destroy_brush(brush_); // don't throw }
BRUSHH operator*() { return brush_; }
private:
BRUSHH brush_;
};
class Select_brush
{
public:
Select_brush(BRUSHH br): brush_(br) {}
~Select_brush() { select_brush(brush_); // don't throw }
private:
BRUSHH brush_;
}
I could continue fiddling with this. Heavens, I could actually try
compiling it! But I figure I might learn something by just posting what
I've got.
[1] names: am I alone? I've never liked the term "auto_ptr()".
Yeah right it's automatic- but what does it *do*? I'm probably way
too late to be saying this... How come Boost have about four
different sorts of "auto_ptr"?
--
Nick Keighley
A ruby trembled. Two tourmaline nets failed to rectify the laser beam.
A diamond noted the error. Both the error and the correction went into
the general computer.
Corwainer Smith "The Dead Lady of Clown Town"