Using throw to replace return in C++ non-void functions
In C++ functions, is it a good practice to replace return
with throw
? For example, I have the following code
// return indices of two numbers whose sum is equal to target
vector<int> twoSum(vector<int>& nums, int target) {
for(int i=0; i<nums.size()-1; ++i)
for(int j=i+1; j<nums.size(); ++j)
{
if(nums[i] + nums[j] == target) return vector<int>{i, j};
}
// return vector<int>{};
throw "no solution";
}
The code above compiles with my GCC 7.2.
Solution 1:
In C++ functions, is it a good practice to replace return with throw?
Return is not something that can be replaced by a throw in general.
In exceptional cases where you have nothing to return, throwing an exception can be a valid way to exit the function.
Whether it is "good practice", and what case is "exceptional" are subjective. For example, for a search function such as yours, it's hardly a surprise that there might not be a solution, and I would argue that throwing would not be appropriate.
There are often other alternatives to throwing. Compare your algorithm with something like std::string::find
that returns the index of the start of a substring. In case where substring does not exist, it returns a "non-value" std::string::npos
. You could do the same and decide that the index -1 is returned when a result is not found. There is also a generic way to add non-value representation to a type in cases where none of the existing representations can be reserved for the purpose: std::optional
.
P.S. A vector is probably not a good choice for returning a pair of numbers. std::pair
might be better, or a custom class if you have good names for the numbers.
Solution 2:
The concepts of this answer are taken from the C++ Programming language by Bjarne Stroustrup.
SHORT ANSWER
Yes, exception-throwing can be used as returning value method. An example is the following for a binary tree search function:
void fnd(Tree∗ p, const string& s)
{
if (s == p−>str) throw p; // found s
if (p−>left) fnd(p−>left,s);
if (p−>right) fnd(p−>right,s);
}
Tree∗ find(Tree∗ p, const string& s)
{
try {
fnd(p,s);
}
catch (Tree∗ q) {
// q->str==s
return q;
}
return 0;
}
However, it should be avoided because:
- they allow you to separate error code from "ordinary code" making your program much more readable, comprehensible and manageable. If you use them as return method, this does not hold anymore.
- there might be inefficiencies because exception implementations rely on the assumption that they are used as error-handling methods.
Apart from that, there are further limitations:
- exceptions must be of copy-able type
- exceptions can handle only synchronous events
- they should be avoided in a time-critical system
- they should be avoided in large old programs in which resource management is an ad hoc mess (free store is unsystematically managed using naked pointers, news and delete) rather than relying on some systematic scheme such as resource handles (strings vectors).
Longer answer
An exception is an object thrown to represent the occurrence of an error. It can be of any type that can be copied but it is strongly recommended to use only user-defined types specifically defined for that purpose. Exceptions allow the programmer to explicitly separate error-handling code from "ordinary code" making the program more readable.
First of all, exceptions are for managing synchronous events, not asynchronous ones. This is one first limitation.
One might think of the exception-handling mechanisms as simply another control structure, an alternative way of returning a value to a caller.
This has some charm but should be avoided because it is likely to cause confusion and inefficiencies. Stroustrup suggests:
When at all possible stick to the "exception handling is an error handling" view. When this is done code is separated into two categories: ordinary code and error handling code. This makes the code more comprehensible. Furthermore, the implementations of the exception mechanisms are optimized based on the assumption that this simple model underlies the use of the exception.
So basically using exceptions to return value should be avoided because
- exception implementation is optimized assuming they are used for error-handling and not for returning values hence they might be inefficient for that;
- They allow separating error code from ordinary code making the code much more readable and comprehensible. Anything that helps preserve a clear model of what is an error and how it is handled should be treasured.
There are programs that for practical or historical reasons cannot use exceptions (neither as error handling so even less):
- A time-critical component of an embedded system where operation must be guaranteed to complete in a specified maximum time. In the absence of tools that can accurately estimate the maximum time for an exception to propagate from
throw
tocatch
alternative error handling methods must be used. - A large old program in which resource management is an ad hoc mess (free store is unsystematically managed using naked pointers,
news
anddelete
) rather than relying on some systematic scheme such as resource handles (string
svector
s).
In the above cases, traditional pre-exception methods are preferred.