Code In question
I have heard (and regurgitated) the C++ exception mantra on both sides of the fence. It has been a while and I just want to centre myself once more, and this discussion is specific to the code I have linked (or low level classes such as containers), and it's dependencies. I used to be a defensive and error_code using C programmer, but it's a tiresome practise and I am programming at a higher level of abstraction now.
So I am rewriting a container class (and it's dependencies) to be more flexible and read better (iterators absent atm). As you can see I am returning enumerated error_codes where I know I will test them at call-site. The containers are for runtime building of AST's, initialize and make read-only. The exceptions are their to prevent the container being used naively (possibly by myself in the future).
I have exceptions all over the place in this class, and they make me feel dirty. I appreciate their use-case. If I had the choice I might turn them off altogether (Boost uses exceptions a lot, and I am building off Boost, and yes I know they can be disabled, but when in Rome....) . I have the choice of replacing them with error_codes but hey, I will not test them , so what is the point ?
Should I replace them with ASSERTS ? What is this bloat people speak off [1] [2] [3]? does every function callsite get extra machinery ? or only those that have a catch clause ? Since I won't catch these exceptions I shouldn't be a victim of this bloatage right ? ASSERTS do not make their way into release builds, in the context of fundamental primitive classes ( -- i.e, containers) does that even matter ? I mean how high are the chances that logic errors would find their way into a final build ?
Since we like to answer focused questions, here is mine: What would you do, and why ? :D
Unrelated Link:Error codes and having them piggy backing in an exception.
edit 2 in this particular case the choice is between ASSERTs and exceptions, I think exceptions make the most sense, as I mentioned above, the container is read only after initialisation, and most of the exceptions are triggered during initialisation.
It's very simple. Avoid error codes like fire and prefer exceptions, unless error code really makes more sense in an individual case. Why? Because exceptions can carry a lot more information — see e.g. Boost.Exception. Because they propagate automatically, so you can't make a mistake of not checking for error condition. Because sometimes, you have to (bailing out of a constructor), so why not be consistent. C++ simply does not offer any better way for signalling error conditions.
Asserts, on the other hand, are used for something completely different — verifying internal state of the code, and assumptions that should always hold true. Failed assertion is always a bug — whereas exception might signal invalid external input, for example.
As for linked guides: forget Google style guide even exists, it's simply terrible, and it's not just my opinion. LLVM — executable size hardly matters, it's not really something you should waste time thinking about. Qt — Qt is lacking in exception safety, but that doesn't mean your code has to, too. Use modern practices, and being exception safe shouldn't be too hard.
To find the solution that is right for you, take the following statements, and pick one from each of the multiple choice options.
This is my library for doing {whatever}.
For the most common answers, will be code with something like 3 assertions for every exception, and 100 exceptions for every error return. The unreasonable answers are, of course, reverse engineered from code written in other ways.
Here's my opinion:
Should I replace them with ASSERTS?
If a client misuses an interface or there is an internal/state error, it is good to use assertions to verify program and state correctness, and prevent clients from misusing the program. If you disable assertions in release and prefer to throw after that point, you can do so. Alternatively, you could add that assertion behavior to the ctor of the exception you throw.
Since I won't catch these exceptions I shouldn't be a victim of this bloatage right ?
I built out an app I wrote with exceptions enabled (I have them disabled by default). The size went from 37 to 44 MB. That's a 19% growth, and the only code that used exceptions was in std
. The meat of that program does no catching or throwing (after all, it can build without exceptions enabled). So, yes, your program's size will grow - even if you don't write a throw
or catch
.
For some programs, that's not a problem. If you use libraries that are designed to use exceptions for error handling (or worse), then you really can't work with them disabled.
...how high are the chances that logic errors would find their way into a final build ?
That depends on the complexity of the program. For a nontrivial program with a moderate to high level of error checking, it's quite easy to trigger an assertion out in the wild (even if it's a false positive).
Since we like to answer focused questions, here is mine: What would you do, and why ? :D
Retroflowing a program which uses exceptions to use error codes is a painful experience. At minimum, I'd add assertions to the existing program.
There are certainly complex programs that do not require exceptions, but why does yours not need them? If you can't come up with a good answer, then you should probably continue writing exceptions in the appropriate places, and add assertions to verify consistency and to ensure the clients don't misuse the programs.
If you do in fact have a good reason to disable exceptions, then you will likely have a lot to rework, and you'll need some form of error handling - whether you use an error code, logging, or something more elaborate is up to you.
template<class errorcode>
struct ForceCheckError {
typedef errorcode codetype;
codetype code;
mutable bool checked;
ForceCheckError()
: checked(true) {}
ForceCheckError(codetype err)
: code(err), checked(false) {}
ForceCheckError(const ForceCheckError& err)
: code(err.code), checked(false) {err.checked = true;}
~ForceCheckError() { assert(checked); }
ForceCheckError& operator=(ForceCheckError& err) {
assert(checked);
code=err.code;
checked=false;
err.checked=true;
return *this;
}
operator codetype&() const {checked=true; return code;}
};
//and in case they get defined recursively (probably via decltype)...
template<class errorcode>
struct ForceCheckError<ForceCheckError<errorcode> >
:public ForceCheckError<errorcode>
{
ForceCheckError()
: checked(true) {}
ForceCheckError(codetype err)
: code(err), checked(false) {}
ForceCheckError(const ForceCheckError& err)
: code(err.code), checked(false) {err.checked = true;}
};
I've never tried it before, but this might be handy, if you prefer error codes, but want to guarantee that they're checked.
ASSERTS should test things that MUST be true, and the program must die immediately if it's false. They should not factor into the exception vs return code debate.
If exceptions are enabled, there is code created in the background to make objects destruct properly during stack unwinding. If you build without exceptions (which is nonstandard), the compiler can emit that code. (In general it's one extra op per function call and one per return, which is nothing) That extra "bloat" will be in every single function that could possibly have to propagate an exception. So, all functions except those with nothrow
, throw()
, or functions that make no function calls and otherwise throw no exceptions.
On the other hand, nobody checks return values unless forced to, via a helper class like above.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With