Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++ error-codes vs ASSERTS vs Exceptions choices choices :( [closed]

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.

like image 878
Hassan Syed Avatar asked Nov 10 '11 23:11

Hassan Syed


4 Answers

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.

like image 187
Cat Plus Plus Avatar answered Sep 22 '22 07:09

Cat Plus Plus


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}.

  • It will be used by {me|volunteers with access to the source|idiot coworkers|people paying me money for a support contract}.
  • If {misused|misconfigured|buggy|systems it relies on fail}, it will {silently break|report a diagnostic in debug mode only|report the issue in a hard to ignore way}.
  • If my code fails, I will {never know|not care|be sad|lose money|get sued}.
  • If code using my code fails, I will {never know|not care|be sad|lose money|get sued}.
  • Correct code that uses my code becoming more complex is {unacceptable|not my problem|amusing}.
  • I consider {a factor of 10|10%|10 clock cycles} to be too high a cost for all that.
  • I am using a compiler from {1977|1997|2007|a thought experiment}.

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.

like image 22
soru Avatar answered Sep 20 '22 07:09

soru


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.

like image 24
justin Avatar answered Sep 23 '22 07:09

justin


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.

like image 35
Mooing Duck Avatar answered Sep 20 '22 07:09

Mooing Duck