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?

asked Feb 19, 2016 at 18:44
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.

answered Feb 19, 2016 at 19:02
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. Commented 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. Commented 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. Commented 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.

answered Feb 19, 2016 at 19:18
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.

answered Feb 19, 2016 at 19:00
4
  • 1
    this answer, would be better, were it limited to, the last paragraph... (← extra commas for your enjoyment :P ) Commented Feb 19, 2016 at 19:18
  • @amon Nice try with the comma splices Commented 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. Commented Jul 28, 2016 at 3:56
  • @immibis: Someone didn't read the rest of the answer. Commented 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.

answered Feb 19, 2016 at 19:08
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). Commented 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). Commented 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. Commented 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. Commented Feb 20, 2016 at 1:43

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.