Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Error handling design for a C++ API

I am writing a C++ API on Windows with VS 2010 which exports several classes from a DLL. We are planing to support other platforms later (MacOS, Linux).

I am currently thinking how to design the error handling. I don't want to use exceptions because of the problem across DLL boundaries (at least in Windows).

I came up with the following three designs so far.

Design 1:

For each method the return value would indicate if the operation succeeded or failed by returning either true/false or pointer/nullptr, respectively. The client might then call GetLastError() to retrieve an error code (enum) which details the last failure.

typedef std::shared_ptr<Object> ObjectPtr;

class APIClass
{
    bool SetSomething(int i);
    bool IsSomethingSet();
    bool DoSomething();
    ObjectPtr GetSomething();

    ErrorCode GetLastError();
}

Design 2:

Each method returns a error code. [out] parameters should be passed as pointers and [in] parameters by value or by (const) reference.

typedef std::shared_ptr<Object> ObjectPtr;

class APIClass
{
    ErrorCode SetSomething(int i);
    ErrorCode IsSomethingSet(bool* outAsk);
    ErrorCode DoSomething();
    ErrorCode GetSomething(ObjectPtr* outObj);
}

Design 3:

Like design 1 but you can pass am optional error code as [out] parameter to each function.

typedef std::shared_ptr<Object> ObjectPtr;

class APIClass
{
    bool SetSomething(int i, ErrorCode* err = nullptr);
    bool IsSomethingSet(ErrorCode* err = nullptr);
    bool DoSomething(ErrorCode* err = nullptr);
    ObjectPtr GetSomething(ErrorCode* err = nullptr);
}

I want to keep the design simple, consistent and clean. Which design would you prefer as a client? Do you have other suggestions for a better design? Can you give some reasons why one design is better or if it's just a matter of taste?

Note: There is a similar question here: C++ API design and error handling. But I do not want to use the solution in the accepted answer there or use something like boost::tuple as return value.

like image 680
Hoschie0815 Avatar asked Feb 17 '12 06:02

Hoschie0815


People also ask

How is error handling done in C?

There are some other ways by which error handling can be done in C language. The header file “error. h” is used to print the errors using return statement function. It returns -1 or NULL in case of any error and errno variable is set with the error code.

Does C have error handling?

The C programming language does not support exception handling nor error handling. It is an additional feature offered by C. In spite of the absence of this feature, there are certain ways to implement error handling in C. Generally, in case of an error, most of the functions either return a null value or -1.

What are the three layers of error handling?

Also provides some best practices to implement error handling in the three layers of SOA i.e. orchestration, mediation and component layers.


3 Answers

Ok, so the no-exceptions constraint makes the problem... I won't question that.

Summarily, which is best depends on the specifics of your APIs: whether the values you tend to return have spare sentinel values, whether you want to force/encourage the developer to more explicitly prepare for and cope with error codes or have it more optional, implicit and/or concise. You might also consider the practices of other libraries you're already using, especially if it's not blindingly obvious to the caller which library a given function is from. Because there's no single best one-size-fits-all answer, this question survives in the C world after so many decades (and C++ has exceptions).

A few discussion points...

Sentinel values (as per your pointers in Design 1) work pretty well when they're anticipated by the caller (e.g. programmers tend to ask themselves "could I get a null pointer? what would it mean?"), intuitive, consistent, and preferably if they can be implicitly converter to booleans then they'd yield true on success! That reads much better, though obviously many OS and standard C library functions return -1 for failure, as 0 is so often valid within the meaningful result set.

Another consideration you've implicitly understood: by relying on state outside the function call you introduce thread and async (signal handling) safety issues. You can document that the developer shouldn't restrict their usage of the object from threaded/async code, or provide thread specific storage for the error codes, but it's an extra dimension of pain. Returning or passing in ErrorCode objects avoids that problem. Passing in ErrorCode objects becomes a constant small burden on the caller, but does encourage them to explicitly think about error handling.

like image 75
Tony Delroy Avatar answered Oct 05 '22 17:10

Tony Delroy


I would go for approach 2, for a number of reasons.

  • it's used in most cross-platform/cross-compiler C++ projects I have seen around the web (including large stuff such as Gecko/Firefox/Thunderbird);
  • if you decide to offer bindings in another programming language, this design won't get too much in the way;
  • clang and g++ offer an attribute warn_unused_result that can be pretty handy with this design to make absolutely sure that errors are not ignored.
like image 37
Yoric Avatar answered Oct 05 '22 19:10

Yoric


As an alternative design I would suggest using a unified wrapper class to handle both return value and error condition in a consistent way.

With C++ usually the return value is not handled via pointer or reference arguments. If I understand you correctly, you don't want to use exceptions for technical reasons, not because you dismiss the design of having an additional channel for communication between caller and callee.

Using a wrapper class for return values will emulate such an additional channel, while giving you the highest level of consistency.

(You could also add an operator T(), but that might be confusing in cases where you have bool as return value.)

template<typename T>
class ReturnValue
{
    public:
    ReturnValue(T&& value, bool succeeded, int error_code):
        value_{std::move(value)},
        succeeded_{succeeded},
        error_code_{error_code}
    /* Some more constructors possibly... */

    T& value() noexcept { return value_; }
    T const& value() const noexcept { return value_; }
    bool succeeded() const noexcept { return succeeded_; }
    operator bool() const noexcept { return succeeded_; }
    int error_code() const noexcept { return error_code_; }

    private:
    T value_;
    bool succeeded_=false;
    int error_code_=0;
};

template<>
class ReturnValue<void>
{
    public:
    ReturnValue(bool succeeded, int error_code):
        succeeded_{succeeded},
        error_code_{error_code}

    bool succeeded() const noexcept { return succeeded_; }
    operator bool() const noexcept { return succeeded_; }
    int error_code() const noexcept { return error_code_; }

    private:
    bool succeeded_=false;
    int error_code_=0;
};
like image 22
TFM Avatar answered Oct 05 '22 19:10

TFM