3

I am working with some low-level (by that I mean code that can't use C++ exceptions and/or the standard library) code that makes heavy use of classes.
Basically, every class contains a bool initialize(); method that is called right after instantiation to initialize all its components, underlying objects and such.
This is done because constructors in C++ can't return a value. Also every method that allocates memory, uses system API that may fail etc. must be checked for positive value.
However, this approach becomes very annoying after a while.
Consider the following code:

bool createHelloWorldString(String* string)
{
    String str1;
    if (!str1.initialize())
        return false;

    String str2;
    if (!str2.initialize())
        return false;
    // Need to check return value as this method may fail because it dynamically allocates memory
    if (!str1.set("Hello "))
        return false;

    if (!str2.set("world"))
        return false;

    if (!str1.append(&str2))
        return false;

    return string->append(&str1);
}

Note: This may not be the best example, but it clearly shows wheres the problem.

Are there any other ways to handle errors or am I stuck with this?

0

4 Answers 4

4

It's not exactly recommended for C++ when you can use all of the language features, but you can use the preprocessor to give you a hand on this. Assuming you don't have to do any cleanup, you can define a macro for the

if (function_call) return false;

bit, and use that instead. If you do have to do some cleanup, I'll have to recommend going against some widely-held folk wisdom (you might have heard of the "Goto considered harmful" bit), and recommend the use of goto, although I would still recommend wrapping it with macros, as well.

As an example, to solve the same problem you're seeing in C, we used to have something like:

#define CHECK(A) if (!(A)) goto End;

bool function_a ()
{
    CHECK(function_b());
    // Do more stuff here

    End:
    // Do cleanup here
}

This gives you something akin to a poor man's in-function exception handling. It's not as complete, but considering you only have error/OK with a boolean return, it should be enough. Back when I worked in a company where C was the language of choice, the error value was an integer from a set of predefined values, and the check function performed logging as well as the checking (at least when compiling for debug). That last bit saved us a lot of hours finding where problems occurred.

3
  • I also expect the "omg goto u evil" crowd to surface over this one, and I also posit that this is one of those cases where it's perfectly reasonable. A good idea, even. Feb 19, 2016 at 19:03
  • @PreferenceBean That's pretty much my expectation, yes. Goto does provoke knee-jerk reactions, but there are a few corner cases where it's a good idea. Stress on the 'few' bit, mind. This happens to be one of them.
    – Iker
    Feb 19, 2016 at 19:04
  • 3
    Even without exceptions, you can still benefit from RAII and put the cleanup code in destructors (unless it can fail). That saves you from the necessity of a cleanup goto. Feb 20, 2016 at 14:38
2

At the code level, you don't have many good options. If you can use C++11 there are some functional-style error-handling techniques available to you, like option chaining.

However, generally low-level code like that uses other means to handle fatal errors in system calls, like memory allocation, segmentation faults, and so forth. This usually involves setting up interrupt registers, and is available even in super-cheap microcontrollers. You should really avail yourself of those facilities, and leave the annoying C-style boolean checking for errors in user-space code.

0

Basically, every class contains a bool initialize(); method that is called right after instantiation to initialize all its components, underlying objects and such.

Yikes!

This is done because constructors in C++ can't return a value.

They don't need to. At the end you either have a fully-formed object that you can use, or you have an exception to catch. What other useful cases exist?

Also every method that allocates memory, uses system API that may fail etc. must be checked for positive value.

Yeah and if those things fail then exception-throwing and RAII will unwind the object creation so you don't have a half-formed object lying around just waiting for disaster.

However, this approach becomes very annoying after a while.

It's annoyed me just reading about it.

I am working with some low-level (by that I mean code that can't use C++ exceptions and/or the standard library) code that makes heavy use of classes.

Oh. Right, okay.

Are there any other ways to handle errors or am I stuck with this?

You're mostly stuck with it by definition, as far as I can tell. There is a solution to make it all better, and that solution was invented precisely to make this all better … but that solution is exceptions.

One thing you could try is the IOStreams approach of setting an "error flag" internally that renders every subsequent operation on that object to be a no-op. Then you don't need all those return false and can probably just do an if (!obj.is_good()) return false; in one, maybe two places towards the end of your function. But you will then be performing a bunch of function calls that don't do anything; whether or not that's a problem for you, only you can tell.

4
  • 1
    this answer, would be better, were it limited to, the last paragraph… (← extra commas for your enjoyment :P )
    – amon
    Feb 19, 2016 at 19:18
  • @amon Nice try with the comma splices Feb 19, 2016 at 20:02
  • "They don't need to. At the end you either have a fully-formed object that you can use, or you have an exception to catch. What other useful cases exist?" - someone didn't read the question.
    – user253751
    Jul 28, 2016 at 3:56
  • @immibis: Someone didn't read the rest of the answer. Jul 28, 2016 at 9:06
0

I agree with this post - the nightmare you're describing is precisely the reason why exceptions were invented, and why convention decrees that the constructor code (initialisation and establishment of invariants) be located in the constructor of an object and not in an initialise() method or some such rot.

If your code must not let exceptions escape for some reason then one solution is to catch the exceptions in the interface layer and to translate them appropriately - i.e. into error return values or whatever.

4
  • It could well be that the code cannot be compiled with exception support, or that it's for a platform whose only compiler doesn't support exceptions (sad, but those still exist).
    – Iker
    Feb 19, 2016 at 19:09
  • 1
    @Iker: Yes, that's why I wrote 'one solution'. There are plenty others, like longjmp() for example. The point is that propagation of error return values tends not to work, except in special circumstances (rigorous coding convention, suitable macros and tools). It's just to easy to make minor or major mistakes with error value propagation, and fiendishly difficult to get full coverage on the error paths. Exceptions, long jumps, signals and whatnot avoid that problem altogether (unless some bozo quenches them).
    – DarthGizka
    Feb 19, 2016 at 19:13
  • That's a good point. In my old workplace it worked pretty well, but we were writing stuff for banks, so we made sure it had a very well established and complete macro system, and the coding conventions were pretty strictly enforced.
    – Iker
    Feb 19, 2016 at 19:25
  • I'll hazard a guess that the O.P. is writing microcontroller code. The variants of C++ for microcontrollers often don't support exceptions at all. Even if they do, exceptions increase the program size substantially, at the same time microcontrollers have a limited amount of program memory. Feb 20, 2016 at 1:43

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.