Paul Licameli
2017-03-22 01:40:25 UTC
I am trying to ensure exception safety, at least wherever it is possible
that read and write operations on block files may fail.
This is harder than just scanning the code for new and delete, and it will
be harder to maintain, if other contibutors are not aware of stricter
demands and introduce new un-safeties. And I don't know how to motivate
every contributor to have the patience read this email to the end.
It is cleaner to write code that throws on error, with a few appropriate
catches that recover from errors; and the many levels of stack in between
not needing to be cluttered with testing and propagating of error values.
But those in-between levels cannot completely ignore errors: they must be
appropriately written to "dodge" in between the throw and the catch, by
ensuring cleanup of side-effects even when exceptions pass.
The general pseudo-code is:
Do X (with side-effects);
Do big, risky Y;
Undo X.
The "Undo X" step should never be done in-line like that, but instead,
should happen within a destructor, and should also be guaranteed not to
emit any other exceptions.
Fixing memory leaks is only one special case where "X" is allocating some
memory and "undo X" is freeing it, and the tool to do that in a destructor
is a smart pointer.
I have lately pushed changes that move other "Undo X" steps into
destructors by other means.
One means is valueRestorer, where X is the setting of a variable.
Another is finally, taken from The C++ Programming Language, 4th edition.
Not a built-in construct as in some other languages, but a function
definable in C++11, that lets you wrap any code at all in a lambda
expression, so that it executes in a destructor of an ad-hoc class.
Another somewhat common thing is locking and unlocking of a mutex, which is
best done with wxMutexLocker, or ODLocker.
Sometimes the "Undo X" step is to be cancelled, but only in case if exiting
the function along a "success" path; that is, the "Do X" step is
conditionally committed. I do this in some places by calling release() on
a generalized std::unique_ptr (one with a second template parameter
supplied, the "deleter").
See my recent commits for abundant examples.
I am afraid someone will have to be a nag, examining all contributions, to
preserve the levels of safety I attained, and I hope others will learn
these not difficult principles well enough, that I don't have to be the
only nag.
Does this exhaust what it means to write exception-safe code? No, one must
also be familiar with the notions of weak, strong, and no-fail guarantees,
in case of multi-step operations that might complete only some steps and
then fail, and I will be referring to these notions too in future code
comments. But this email is long enough now.
PRL
that read and write operations on block files may fail.
This is harder than just scanning the code for new and delete, and it will
be harder to maintain, if other contibutors are not aware of stricter
demands and introduce new un-safeties. And I don't know how to motivate
every contributor to have the patience read this email to the end.
It is cleaner to write code that throws on error, with a few appropriate
catches that recover from errors; and the many levels of stack in between
not needing to be cluttered with testing and propagating of error values.
But those in-between levels cannot completely ignore errors: they must be
appropriately written to "dodge" in between the throw and the catch, by
ensuring cleanup of side-effects even when exceptions pass.
The general pseudo-code is:
Do X (with side-effects);
Do big, risky Y;
Undo X.
The "Undo X" step should never be done in-line like that, but instead,
should happen within a destructor, and should also be guaranteed not to
emit any other exceptions.
Fixing memory leaks is only one special case where "X" is allocating some
memory and "undo X" is freeing it, and the tool to do that in a destructor
is a smart pointer.
I have lately pushed changes that move other "Undo X" steps into
destructors by other means.
One means is valueRestorer, where X is the setting of a variable.
Another is finally, taken from The C++ Programming Language, 4th edition.
Not a built-in construct as in some other languages, but a function
definable in C++11, that lets you wrap any code at all in a lambda
expression, so that it executes in a destructor of an ad-hoc class.
Another somewhat common thing is locking and unlocking of a mutex, which is
best done with wxMutexLocker, or ODLocker.
Sometimes the "Undo X" step is to be cancelled, but only in case if exiting
the function along a "success" path; that is, the "Do X" step is
conditionally committed. I do this in some places by calling release() on
a generalized std::unique_ptr (one with a second template parameter
supplied, the "deleter").
See my recent commits for abundant examples.
I am afraid someone will have to be a nag, examining all contributions, to
preserve the levels of safety I attained, and I hope others will learn
these not difficult principles well enough, that I don't have to be the
only nag.
Does this exhaust what it means to write exception-safe code? No, one must
also be familiar with the notions of weak, strong, and no-fail guarantees,
in case of multi-step operations that might complete only some steps and
then fail, and I will be referring to these notions too in future code
comments. But this email is long enough now.
PRL