On Aug 1, 4:04 pm, Juha Nieminen <nos...@thanks.invalid> wrote:
James Kanze wrote:
In all but a few, very rare cases, of course, exceptions are
preferable to these alternatives.
Of course if the constructor of a class can throw exceptions,
some care has to be taken when using such a class.
Any time something can throw exceptions, some care has to be
taken when using it. More to the point: any time an operation
can fail, some care has to be taken when using it.
Example:
class A
{
int* table;
B b; // constructor can throw
// Disallow copying and assignment here, or whatever
public:
A():
table(new int[100]) // bad!
{}
~A() { delete[] table; }
};
The problem with the above code is that if the constructor of
'b' throws an exception, the table is leaked.
But the issue above has nothing to do with constructors. You
get exactly the same problem if you write:
void
f()
{
int* table = new int[ 100 ] ;
g() ; // g() can throw...
}
Exception safety requires some thought. The alternative ways of
reporting errors do as well. The general rule is that if there
is any chance of the client code being able to handle an error
locally, you use a return value, rather than an exception.
Except when return values aren't possible: constructors and
overloaded operators, for example. In the case of constructors,
the exception has an additional advantage; the object we
couldn't construct isn't there, so it can't be used by error.
You might try to avoid the problem with an A constructor like this:
A() try:
table(new int[100])
{}
catch(...)
{
delete[] table;
}
This is not ok, though. AFAIK trying to access a member
variable from a constructor catch block is undefined behavior.
A practical case can be constructed by switching the places of
'table' and 'b' inside A. In this case 'b' is constructed
first, and if it throws, the catch block will attempt to
delete an uninitialized pointer (which might not be null!),
which may cause a segmentation fault.
I'm not sure, because I've never heard of anyone trying to do
this.
The standard way of avoiding the problem (in this case) is to
replace the "int* table" in the class with std::vector<int>.
And that doesn't cause any problems.
The proper way of implementing the constructor is:
A():
table(0) // ok
{
table = new int[100];
}
Now if b throws, nothing will have been allocated yet for the
table, so nothing leaks. (This is so even if we switch the
order of 'table' and 'b'.)
No, the proper way of implementing the constructor is, as I
said, to replace the int* with std::vector< int >. And more
generally, to ensure that any member of the class which needs
cleaning up is a proper class itself, with a destructor which
does the cleaning up.
--
James Kanze (GABI Software) email:james.ka...@gma=