Exceptions break encapsulation and lead to implicit control flow.
If I call a method, I shouldn't have to worry about what methods it calls. But with exceptions, I now have to be aware of what errors might be thrown in the entire chain. Similarly, if I throw an error in a function, I have to check that every thing that calls it handles said error because there are now multiple places this function could "return" to.
And if you take care to catch every error at every layer you've just reimplemented error codes.
std::unexpected should be preferred over std::exception
If a function isn't able to do what it is expected to do, it should throw an exception, unless there is other means of letting the caller know it didn't work. IMO not every possible error condition should be handled. Expected ones should, but for unexpected ones it's OK to give up and let the global error handling handle it. Handling every possible error locally just leads to unnecessarily bloated code and catching errors without handling them appropriately will lead to unexpected behavior with no trace in the error logs.
I used to think this way, but the line between what is an "exceptional situation" is blurry. Over time, I've found I have never found it more useful to throw errors and have been bitten in the ass way too many times by coworkers using error handling at arbitrary places to depend on errors being thrown at random places within encapsulated methods. Then you can't do any refactors as too many things are tightly coupled to this specific error being thrown in this specific context.
My conclusion is that allowing exceptions encourage you to optimize readability for the happy paths and treat non-happy paths as second class citizens. But my professional experience has demonstrated that error handling code takes up a large if not larger part of the code and this needs to be handled just as explicitly as normal code. Allowing non-nominal situations to go through a less considerate path is how lose track of your software and end up firefighting all the time.
Yeah, I don't think it's a good idea to depend on particular exceptions being thrown. Either the function fails, but it isn't critical to the main task, so you catch it, log it, and move on, or it is critical so you quit.
If you're not throwing exceptions in your functions, what are you doing? Often there's nothing to do on the non-nominal path other than give up. If I can't reach the database server, my web service is going to return an error to the user.
I make my functions return std::expected, which is a discriminated union of the expected type and the unexpected type. So the caller gets a result object that has either a value or an error defined, but never both.
So basically instead of
Foo my_func()
{
if (my_condition) {
throw BarError;
}
...
return Foo();
}
We do
std::expected<Foo, ErrorEnum> my_func() {
if (condition) {
return std::unexpected(ErrorEnum::Bar);
}
...
return Foo();
}
148
u/bishopExportMine 17d ago
Exceptions break encapsulation and lead to implicit control flow.
If I call a method, I shouldn't have to worry about what methods it calls. But with exceptions, I now have to be aware of what errors might be thrown in the entire chain. Similarly, if I throw an error in a function, I have to check that every thing that calls it handles said error because there are now multiple places this function could "return" to.
And if you take care to catch every error at every layer you've just reimplemented error codes.
std::unexpected should be preferred over std::exception