tags:

views:

1076

answers:

6

We have recently been faced with the problem of porting our C++ framework to an ARM platform running uClinux where the only vendor supported compiler is GCC 2.95.3. The problem we have run into is that exceptions are extremely unreliable causing everything from not being caught at all to being caught by an unrelated thread(!). This seems to be a documented bug, i.e. here and here.

After some deliberation we decided to eliminate exceptions altoghether as we have reached a point where exceptions do a lot of damage to running applications. The main concern now is how to manage cases where a constructor failed.

We have tried lazy evaluation, where each method has the ability to instantiate dynamic resources and return a status value but that means that every class method has to return a return value which makes for a lot of ifs in the code and is very annoying in methods which generally would never cause an error.

We looked into adding a static create method which returns a pointer to a created object or NULL if creation failed but that means we cannot store objects on the stack anymore, and there is still need to pass in a reference to a status value if you want to act on the actual error.

According to Google's C++ Style Guide they do not use exceptions and only do trivial work in their constructors, using an init method for non-trivial work (Doing Work in Constructors). I cannot however find anything about how they handle construction errors when using this approach.

Has anyone here tried eliminating exceptions and come up with a good solution to handling construction failure?

A: 

Regarding the Google reference (you couldn't find how they handled errors in the constructor):

The answer to that part is that if they only do trivial work in the constructor, then there are no errors. Because the work is trivial, they are pretty confident (backed up by thorough testing, I'm sure) that exceptions just won't be thrown.

Joel Coehoorn
It's not a matter of things throwing exceptions, it's a matter of how to handle errors in constructors without exceptions. Which I'm pretty sure is a problem Google would run into as well sooner or later.
David Holm
The discipline to write nothrow constructors isn't really that different from the discipline required to write nothrow swap: the same kind of analysis "proves" that it can't throw. If your swap function has to "handle errors", then you must redesign your class, and the same applies here to ctors.
Steve Jessop
Then just s/exceptions/errors/. The point is still valid: if you only do trivial work you can be reasonably sure it doesn't happen.
Joel Coehoorn
Yes, sorry, I agree with what you say, I'm just chipping in with the observation that "ensuring there are no errors to handle" isn't completely alien, since it's something C++ programmers already do in swap, where you don't want to resort to throwing exceptions (or catching them, ideally).
Steve Jessop
A: 

I guess to a large extent, it depends what type of exceptions are typically occurring. My assumption is they are primarily resource related. If this is the case, a solution that I have used previously on an embedded C system was to allocate / commit all potentially required resources at the start of the program. Thus I knew that all required resources were available at execution time rather than during the run. It's a greedy solution that may interfere with interoperability with other software, but it worked quite well for me.

Shane MacLaughlin
I don't think it would work for our application because it reacts to events during runtime which will cause resources to be instantiated which we do not know about at launch.
David Holm
A: 

If a constructor is only doing trivial things like initializing POD variables (and calling other trivial constructors implicitly), then it can't possibly fail. See the C++ FQA; see also why you shouldn't use C++ exceptions.

Adam Rosenfield
Now if only an object-oriented application could consist of only POD-classes :(
David Holm
Quoting C++ FQA has little sense in this context. It was written with the sole purpose of convincing people not to use C++ at all, and it is not an option here.
Nemanja Trifunovic
I'm sorry, but I can't let a question that quotes the FQA as a good source sit at two upvotes. It is a sarcastic text (with a serious motive of getting people to stop using C++), and misinterpreting it can have bad results.
coppro
+8  A: 

Generally you end up with code like this for objects on the stack:

MyClassWithNoThrowConstructor foo;
if (foo.init(bar, baz, etc) != 0) {
    // error-handling code
} else {
    // phew, we got away with it. Now for the next object...
}

And this for objects on the heap. I assume you override global operator new with something that returns NULL instead of throwing, to save yourself remembering to use nothrow new everywhere:

MyClassWithNoThrowConstructor *foo = new MyClassWithNoThrowConstructor();
if (foo == NULL) {
    // out of memory handling code
} else if (foo->init(bar, baz, etc) != 0) {
    delete foo;
    // error-handling code
} else {
    // success, we can use foo
}

Obviously if you possibly can, use smart pointers to save having to remember the deletes, but if your compiler doesn't support exceptions properly, then you might have trouble getting Boost or TR1. I don't know.

You also might want to structure the logic differently, or abstract the combined new and init, to avoid deeply-nested "arrow code" whenever you're handling multiple objects, and to common-up the error-handling between the two failure cases. The above is just the basic logic in its most painstaking form.

In both cases, the constructor sets everything to default values (it can take some arguments, provided that what it does with those arguments cannot possibly fail, for instance if it just stores them). The init method can then do the real work, which might fail, and in this case returns 0 success or any other value for failure.

You probably need to enforce that every init method across your whole codebase reports errors in the same way: you do not want some returning 0 success or a negative error code, some returning 0 success or a positive error code, some returning bool, some returning an object by value that has fields explaining the fault, some setting global errno, etc.

You could perhaps take a quick look at some Symbian class API docs online. Symbian uses C++ without exceptions: it does have a mechanism called "Leave" that partially makes up for that, but it's not valid to Leave from a constructor, so you have the same basic issue in terms of designing non-failing constructors and deferring failing operations to init routines. Of course with Symbian the init routine is permitted to Leave, so the caller doesn't need the error-handling code I indicate above, but in terms of splitting work between a C++ constructor and an additional init call, it's the same.

General principles include:

  • If your constructor wants to get a value from somewhere in a way that might fail, defer that to the init and leave the value default-initialised in the ctor.
  • If your object holds a pointer, set it to null in the ctor and set it "properly" in the init.
  • If your object holds a reference, either change it to a (smart) pointer so that it can null to start with, or else make the caller pass the value into the constructor as a parameter instead of generating it in the ctor.
  • If your constructor has members of object type, then you're fine. Their ctors won't throw either, so it's perfectly OK to construct your members (and base classes) in the initializer list in the usual way.
  • Make sure you keep track of what's set and what isn't, so that the destructor works when the init fails.
  • All functions other than constructors, the destructor, and init, can assume that init has succeeded, provided you document for your class that it is not valid to call any method other than init until init has been called and succeeded.
  • You can offer multiple init functions, which unlike constructors can call each other, in the same way that for some classes you'd offer multiple constructors.
  • You can't provide implicit conversions that might fail, so if your code currently relies on implicit conversions which throw exceptions then you have to redesign. Same goes for most operator overloads, since their return types are constrained.
Steve Jessop
Some parts of boost work but we have our own smart pointer already so that isn't a problem. So far we aren't considering std::bad_alloc a problem because if we run out of memory we have bigger issues.Thanks for all the good suggestions!
David Holm
In this situation, using nested if/else to check for errors, using the goto keyword can be used to reduce the nesting depth making the code easier on the eye. So, I'd do "if (something failed) goto error_handler;"
Skizz
It's probably worth pointing out that this pattern is known as "two-phase construction"
ChrisN
@ChrisN: yes, that stirs a memory. I follow the principle that unless a term appears in a class or function name, I'm at liberty to forget it. And if it does appear in a name, then I can find it in the docs, so again I'm at liberty to forget it ;-)
Steve Jessop
+1  A: 

If you really can't use exceptions, you can also write a construction macro doing what onebyone proposed always. So you don't get into the hassle of doing this creation/init/if cycle all the time and most important, you never forget to initialize an object.

struct error_type {
    explicit error_type(int code):code(code) { }

    operator bool() const {
        return code == 0;
    }

    int get_code() { return code; }
    int const code;
};

#define checked_construction(T, N, A) \
   T N; \
   if(error_type const& error = error_type(N.init A))

The error_type struct will invert the condition, so that errors are checked in the else part of the if. Now write an init function that returns 0 on success, or any other value indicating the error code.

struct i_can_fail {
    i_can_fail() {
        // constructor cannot fail
    } 

    int init(std::string p1, bool p2) {
        // init using the given parameters
        return 0; // successful
    } 
};

void do_something() {
    checked_construction(i_can_fail, name, ("hello", true)) {
        // alright. use it
        name.do_other_thing();
    } else {
        // handle failure
        std::cerr << "failure. error: " << error.get_code() << std::endl;
    }

    // name is still in scope. here is the common code
}

You can add other functions to error_type, for example stuff that looks up what the code means.

Johannes Schaub - litb
+1  A: 

You could use a flag to keep track of whether the constructor failed. You might already have a member variable that's only valid if the constructor succeeds, e.g.

class MyClass
{
public:
    MyClass() : m_resource(NULL)
    {
        m_resource = GetResource();
    }
    bool IsValid() const
    {
        return m_resource != NULL;
    }
private:
    Resource * m_resource;
};

MyClass myobj;
if (!myobj.IsValid())
{
    // error handling goes here
}
Mark Ransom