1

I have a C++ API which throws exceptions in error conditions. Usually, 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. This method has limitations, for example if you need to 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.

Due to this, I choose to throw an exception in my API when an error occurs in a function.

Is this an acceptable usage of exceptions in C++?

Also, In some functions in my API (eg. authenticate()), I have two options.

  1. return bool to indicate success.
  2. return void and throw an exception if failed.

If first option is used, it is not consistent with other functions because they all throw exceptions. Also, it is difficult to indicate what is the error.

So is it ok to use second method in such functions too?

In following answer, it is mentioned that it is bad to use C++ exceptions for controlling program flow. Also, I have heard the same elsewhere too.

https://stackoverflow.com/a/1736162/1015678

Does my usage violates this? I cannot clearly identify whether I am using exceptions for controlling program flow here.

Community
  • 1
  • 1
Lahiru Chandima
  • 22,324
  • 22
  • 103
  • 179
  • There's no clear yes/no answer, which is why the linked answer says "rule of thumb". To put it another way: normal operation of your program should not involve any exceptions being thrown. – M.M Dec 16 '14 at 04:28
  • "if you need to 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" - yes you can, it just isn't *convenient* any longer. An out-param by reference or address can receive such a result (the error state or the value, take your pick) and you can still retain the return value for whichever wasn't by-param. That pattern does, however, get annoying to both maintain and use in a relatively short period of time. It, like most of this question, is a matter of opinion. – WhozCraig Dec 16 '14 at 04:29
  • Re. your first paragraph, the function could return either the status code or the integer via a reference parameter, or return a `tuple`. Using an exception just to avoid adding a reference parameter is bad design. – M.M Dec 16 '14 at 04:30
  • Using exceptions looks much cleaner for me compared to passing reference arguments for error codes and I have seen it being used heavily in java world. So is this specifically bad in C++ or it is generally bad in any programming language? – Lahiru Chandima Dec 16 '14 at 04:41
  • the rule of thumb is not to not control program flow, that's what they do. the rule of thumb is to not use exception to control **normal** flow, ie, use only for error-conditions – sp2danny Dec 16 '14 at 05:40

2 Answers2

4

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.

Tony Delroy
  • 102,968
  • 15
  • 177
  • 252
1

For something like authenticate(), I'd expect you to return a bool if you were able to compute a true/false value for the authentication, and throw an exception if something prevented you from doing that. The comment about using exceptions for flow control is suggesting NOT doing something like:

try {
   ...
   authenticate();
   // rely on the exception to not execute the rest of the code.
   ...
} catch (...) { ... }

For instance, I can imagine an authenticate() method that relies on contacting some service, and if you can't communicate with that service for some reason, you don't know if the credentials are good or bad.

Then again, the other major rule of thumb for APIs is "be consistent". If the rest of the API relies on exceptions to serve as the false value in similar cases, use that, but to me, it's a little on the ugly side. I'd lean toward reserving exceptions for the exceptional case - i.e. rare, shouldn't ever happen during normal operations, cases.

Charlie
  • 1,487
  • 10
  • 9