non-throwing, non-allocating exception_wrapper
Summary:
The purpose of this reimplementation of `exception_wrapper` is threefold:
- Make `exception_wrapper` smaller. It goes from 48 bytes to 24.
- Give it `noexcept` ~~copy and~~ move
- Store small exception objects in an internal buffer; i.e., with zero allocations.
The ultimate goal is to change `folly::Try<T>` to a thin wrapper over `folly::Expected<T, exception_wrapper>`. (Currently, it stores the `exception_wrapper` on the heap.)
As part of this redesign, I:
- Remove `exception_wrapper::getCopied`. The user shouldn't care how the `exception_wrapper` stores the exception.
- Remove `exception_wrapper::operator==`. It was only used in 2 places in test code. The existing semantics (return true IFF two `exception_wrapper`s point to the //same// exception object) prevented the small-object optimization.
- Add new `handle()` API that behaves like cascading `catch` clauses. For instance:
```lang=c++
exception_wrapper ew = ...;
ew.handle(
[&](const SomeException& e) { /*...*/ },
[&](const AnotherException& e) { /*...*/ },
[&](...) { /* catch all*/ }, // yes, lambda with ellipses works!
```
- Add a `type()` member for accessing the `typeid` of the wrapped exception, if it's known or can be determined with a `catch(std::exception&)`.
This table shows the percent improvement for the exception_wrapper_benchmark test:
| Test | Percent improvement (gcc-5) | Percent improvement (gcc-4)
| ----- | ----- | -----
| exception_wrapper_create_and_test | 14.33% | -6.50%
| exception_wrapper_create_and_test_concurrent | 11.91% | 20.15%
| exception_wrapper_create_and_throw | -0.82% | -0.25%
| exception_wrapper_create_and_cast | 15.02% | 14.31%
| exception_wrapper_create_and_throw_concurrent | 18.37% | 8.03%
| exception_wrapper_create_and_cast_concurrent | 28.18% | -10.77%
The percent win for gcc-5 is 15% on average. The non-throwing tests show a greater win since the cost of actually throwing an exception drowns out the other improvements. (One of the reasons to use `exception_wrapper` is to not need to throw in the first place.) On gcc-4, there is roughly no change since the gcc-4 standard exceptions (`std::runtime_error`, std::logic_error`) are non-conforming since they have throwing copy operations.
Reviewed By: yfeldblum
Differential Revision:
D4385822
fbshipit-source-id:
63a8316c2923b29a79f8fa446126a8c37aa32989