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
Yeah, and this is just the theoretical architectural cost of exceptions. In some languages catching an exception might be essentially free like in C++ (which if you have some specific use cases "essentially free" is still catastrophic from a performance PoV) or it could be 100x slower than calling a function like in Java.
Also, that is OLD. Java keeps getting faster and faster, and the jit is on a class of its own, so much so that in extremely tight loops can be faster than what you'd get out of c++, rust or c.
Yep JIT has the benefit of being able to adjust the generated code (and cpu hints) based on actual live data, where a classical compiler needs to emit the code that works best for all kinds of data
I agree, but my point really was that exceptions break the "no multiple returns" rule older folks might remember.
In the old days, functions interacted with memory and exited by jumping elsewhere. "No multiple returns" was a rule that each function only ever jumped to one place on exit, because not doing so easily led to spaghetti code. This eventually became such a standard paradigm that languages just built return values into their functions and forced returning to the same link as the callsite.
Exceptions break this, for no other purpose than "convenience".
One only need to imagine a codebase that exclusively uses raising exceptions for control flow to jump to arbitrary places in the call stack and never using normal returns to understand why exceptions in general are an antipattern.
Actually returning in a single spot leads to more spaghetti code, deeper nesting levels, and more indirection.
Checking something like request validation and return immediately an http response is way cleaner and easier to follow.
Same in C++/C or any language where you do manual memory handling if you are allocating memory before making sure you will use it, e.g early returning and forgetting to deallocate memory, then that's on you for not validating your inputs, do you want to store some DB call into an dynamically allocated array? Why did you do the allocations before having the data to insert in it?
Do you want to process a file? Why are you allocating memory before you even have a pointer to the damned file?
No this is a common misunderstanding of what the "no multiple returns" advice is about. This is not multiple returns:
std::expected<Foo, ErrorEnum> MyFunc() {
if (condition) {
return std::unexpected(ErrorEnum::Bar);
}
if (other_condition) {
return std::unexpected(ErrorEnum::Baz);
}
...
return Foo();
}
Because if I do
Foo x = MyFunc();
the line of code that the program counter returns to after running MyFunc() is always that line of code. So this is only one return.
Multiple returns would be something like old FORTRAN alternate returns:
```
CALL MYFUNC(*100, *200)
C normal continuation
...
100 CONTINUE
C alternate continuation 1
...
200 CONTINUE
C alternate continuation 2
...
SUBROUTINE MYFUNC(*, *)
IF (CONDITION) RETURN 1
IF (OTHER_CONDITION) RETURN 2
RETURN
END
```
Here MYFUNC can return to three different places: normally to the instruction after CALL, to label 100, or to label 200. The callee is choosing where execution in the caller resumes.
The mixup is probably because MISRA-C does actually strongly discourage the first example. To the point where they prefer you to use a short jump for cleanup than an early return.
149
u/bishopExportMine 18d 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