6

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.

Hassan Syed
  • 20,075
  • 11
  • 87
  • 171
  • It is possible to make a class that will hold an error code and throw if the error code is not checked. I've been meaning to write up the exact process for a long time now. – Mark Ransom Nov 10 '11 at 23:39
  • 1
    I don't feel like reading 364 lines of code. Could you tell us the relevant line numbers? – Andrew Shepherd Nov 10 '11 at 23:40
  • @Andrew exception emitions have the word "throw" in them, and the only strings (the red looking stuff) are exception messages :D – Hassan Syed Nov 10 '11 at 23:41
  • I vastly prefer exceptions over error codes. Just the removal of error handling code from main line code alone makes them worth their weight in gold IMO. But keeping in mind exceptions should be used for exceptional cases. For example an `indexOf` method should probably return -1 if an item is not found instead of throw an exception. – Matt Greer Nov 10 '11 at 23:41
  • @mark boost-system has platform neutral error_code namespace partitioning, and boost-exception allows you to piggy back arbitrary data with your exceptions. I have used this method in some previous code, works well. – Hassan Syed Nov 10 '11 at 23:43
  • @mark I found the code. I have added a link showing how it fits together. You have to instantiate an instance of your error_code type. Its in a translation unit somewhere in the /src directory :D – Hassan Syed Nov 10 '11 at 23:53
  • 4
    Exceptions, assertions and error codes all do different things, and you can't replace one for another. Use an assertion when you *know* that a condition must be true, and it could only be false due to a logic error. An assertion means "here's a bug, fix it". An exception signals an exceptional situation which should not arise in the expected, usual work flow (like "file not found" or "out of memory"). Error codes are a way to abuse a return value that was meant for something else to signal state information. – Kerrek SB Nov 11 '11 at 00:00
  • @Kerrek SB - I think your point is really good and should be an answer rather than a comment. – Andrew Shepherd Nov 11 '11 at 00:11
  • @AndrewShepherd: Thanks :-) I didn't really read the question thoroughly, though, so I don't feel in a position to post an answer! – Kerrek SB Nov 11 '11 at 00:15

4 Answers4

13

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.

Cat Plus Plus
  • 125,936
  • 27
  • 200
  • 224
  • I think the external output (runtime) vs. Errors which crop up due to incorrect usage / orchestration of software components is the main point that addresses my uncertainty :) – Hassan Syed Nov 11 '11 at 03:02
4

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.

soru
  • 5,464
  • 26
  • 30
1
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.

Mooing Duck
  • 64,318
  • 19
  • 100
  • 158
  • 1
    `if (!checked)`, not `if (checked == false)`. Also, throwing from destructors is a bad, bad idea — imagine there are two objects of this type in scope, and you don't check either of them — destructor of the first throws, which triggers unwind, which destroys the second, which throws, which ends up in `std::terminate`. – Cat Plus Plus Nov 11 '11 at 00:58
  • I had a similar helper class once... I added "successful" state [default constructed] that did not throw an exception even if it is not checked. Cat++s warning about exceptions in destructors is the primary reason I don't use it... I've considered adding an `unchecked_exception` check, but there are other corner cases that make the whole thing a hassle, so I never bothered. – Dennis Zickefoose Nov 11 '11 at 01:25
  • Don't know why I didn't change this before, switched the throw to an `assert`. @DennisZickefoose: I definitely do _not_ want that. The error code might be "no error" but the caller MUST check to see. – Mooing Duck Nov 29 '11 at 17:21
1

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.

Community
  • 1
  • 1
justin
  • 104,054
  • 14
  • 179
  • 226