the method I have seen in C++ APIs to notify errors is by special return codes and functions which returns last error string which can be checked if method returns an error code.
Sometimes that's done for good reasons, but more often when you see that the C++ library wraps an older C library, has been written by someone more comfortable with C, written for client coders more comfortable with C, or is written for interoperability with C.
return an integer from a function and the whole integer value range is valid for return values so you can't return an error code.
Options include:
exceptions
returning with a wider type (e.g. getc()
returns an int
with -1
indicating EOF).
returning a success flag alongside the value, wrapped in a boost::optional
, pair
, tuple
or struct
having at least one of the success flag and/or value owned by the caller and specified to the function as a non-const
by-reference or by-pointer parameter
Is this an acceptable usage of exceptions in C++?
Sounds ok, but the art is in balancing the pros and cons and we don't know whether it's optimally convenient and robust for client code calling your functions. Understanding their expectations in key, which will partly be formed based on their overall C++ experience, but also from the rest of your API and any other APIs shipped alongside yours, and even from other APIs for other libraries they're likely to be using in the same apps etc..
Consider too whether the caller of a function is likely to want to handle the success or failure of that function in the context of the call, separately from other possible failures. For example, sometimes it's easier for client code to work with functions returning boolean success values:
if (fn(1) && !fn(2))
fn(3);
try
{
fn(1);
try
{
fn2();
}
catch (const ExpectedExceptionFromFn2Type&)
{
fn3();
}
}
catch (const PossibleExceptionFromFn1Type&)
{
// that's ok - we don't care...
}
But other times it can be easier with exceptions:
try
{
My_X x { get_X(99) };
if (x.is_happy(42))
x += next_prime_after(x.to_int() * 3);
}
catch (std::exception& e)
{
std::cerr << "oops\n";
}
...compared to...
bool success;
My_X x;
if (get_X(&x, 99)) {
if (x.is_valid() {
bool happy;
if (x.can_get_happy(&happy, 42) && happy) {
int my_int;
if (x.can_convert_to_int(&my_int)) {
if (!x.add(next_prime_after(x.to_int() * 3))) {
std::cerr << "blah blah\n";
return false;
} else { cerr / return false; }
} else { cerr / return false; }
} else { cerr / return false; }
} else { cerr / return false; }
} else { cerr / return false; }
(Exactly how bad it gets depends on whether functions support reporting an error, or can be trusted to always work. That's difficult too, because it something happens that makes it possible for a function to start failing (e.g. it starts using a data file that could potentially be missing), if client code didn't already accept and check an error code or catch exceptions, then that client code may need to be reworked once the potential for errors is recognised. That's less true for exceptions, which - when you're lucky - may propagate to some already-suitable client catch statement, but on the other hand it's a risky assuming so without at least eyeballing the client code.)
Once you've considered whatever you know about client usage, there may still be some doubt about which approach is best: often you can just pick something and stick to it throughout your API, but occasionally you may want to offer multiple versions of a function, e.g.:
bool can_authenticate();
void authenticate_or_throw();
...or...
enum Errors_By { Return_Value, Exception };
bool authenticate(Errors_By e) { ... if (e == Exception) throw ...; return ...; }
...or...
template <class Error_Policy>
struct System
{
bool authenticate() { ... Error_Policy::return_or_throw(...); }
...
}
Also, In some functions in my API (eg. authenticate()
), I have two options.
As above, you have more than 2 options. Anyway, consistency is very important. It sounds like exceptions are appropriate.
mentioned that it is bad to use C++ exceptions for controlling program flow
That is precisely what exceptions do and all they can be used for, but I do understand what you mean. Ultimately, striking the right balance is an art that comes with having used a lot of other software, considering other libraries your clients will be using alongside yours etc.. That said, if something is an "error" in some sense, it's at least reasonable to consider exceptions.