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.
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.