-
Notifications
You must be signed in to change notification settings - Fork 1.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add scope_exit utilities #3840
Add scope_exit utilities #3840
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍 Looks great! Left some minor comments, but nothing that should hold this up. Thanks for coding this up; as you know I'll have a good place to use it already.
scope_exit(scope_exit&& rhs) noexcept( | ||
std::is_nothrow_move_constructible_v<EF> || | ||
std::is_nothrow_copy_constructible_v<EF>) | ||
: exit_function_{std::forward<EF>(rhs.exit_function_)} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since rhs
is an rvalue, not a universal ref, shouldn't this be std::move
, not std::forward
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
forward
is used because EF
is allowed to be an lvalue reference to a functor. So this is a move if EF
is an object, and a reference binding if EF
is an lvalue reference. With std::move
this would fail to compile if EF
is a lvalue reference.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Makes sense. Thanks for the explanation.
src/ripple/basics/scope.h
Outdated
std::enable_if_t< | ||
!std::is_same_v<std::remove_cv_t<EFP>, scope_exit> && | ||
std::is_constructible_v<EF, EFP>>* = | ||
0) noexcept(std::is_nothrow_constructible_v<EF, EFP> || std::is_nothrow_constructible_v<EF, EFP&>) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's a shame the enable_if
has to be a parameter - which hides it away from where we see it in the rest of the rippled code. But I think we're forced to do that because this is a constructor.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right. And requires
isn't in our toolbox yet. (requires
is going to be great...)
src/ripple/basics/scope.h
Outdated
try : exit_function_ | ||
{ | ||
std::forward<EFP>(f) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would have left the try block off myself (which really means leaving off this whole overload). The idea that the move constructor for the function would throw just isn't going to happen in rippled code. But I know this is meant to mimic standard library code, not ripple code. No change needed. I'll also note I think this is the first time I've seen function try block used in practice.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's also the first time I've used a function try block. :-)
It looks like all of the gcc builds are failing CI. I didn't look at them all, but one of the failing gcc builds says this:
A quick scan of some of the other gcc builds showed a similar complaint. The Windows builds show a similar complaint:
|
Ah. Yes, when the functor construction can't throw an exception the function try catch is superfluous. And instead of optimizing it away the compiler is whining about it. :-\ The only way I see to fix this is to create two constructors: one constrained when noexcept is false and the other when noexcept is true, and only put the try/catch on the noexcept false version. And that's just ugly enough for me to balk. I think instead the best path forward is to eliminate the try/catch, and static_assert that the construction is noexcept. I'll test that a bit before I push it. |
of scope_exit and scope_fail.
Ok, this is working on clang. This means (for example), one can not construct Best use case is to use a lambda that has a noexcept constructor and move constructor. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍 Looks great. I'm doubtful that anyone at Ripple will use scope_fail
or scope_success
, but since we're exercising things for the committee I think it's okay to include them.
I left a few notes for you to consider on the unit tests. But they are at your discretion. Thanks for doing this!
// permitted to throw an exception. This was done because some compilers | ||
// did not like the superfluous try/catch in the common instantiations | ||
// where the construction was noexcept. Instead a static_assert is used | ||
// to enforce this restriction. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice comment. Thanks.
src/test/basics/scope_test.cpp
Outdated
} | ||
BEAST_EXPECT(i == 1); | ||
{ | ||
scope_exit x{[&i]() { i = 3; }}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What would you think of changing this test to i += 2
? That would demonstrate that exactly one of the two scope_exit
objects executed.
src/test/basics/scope_test.cpp
Outdated
BEAST_DEFINE_TESTSUITE(scope, ripple_basics, ripple); | ||
|
||
} // namespace test | ||
} // namespace ripple |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's no newline at the end of scope_test.cpp. Might be nice to add one.
} | ||
} | ||
BEAST_EXPECT(i == 5); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oops. One other note that I'd apparently failed to save. All of the tests currently use lambdas. What would you think of testing a bog standard function as well? Perhaps something like...
struct scope_test : beast::unit_test::suite
{
inline static int testVal = 0;
// static function to execute in tests
static void
scopeFunc()
{
testVal = 10;
}
...
testVal = 0;
{
scope_exit x{scopeFunc};
}
BEAST_EXPECT(testVal == 10);
That works, BTW. It just seems like we should exercise it in the unit tests. Your call...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
High Level Overview of Change
Add scope_exit utilities from Library Fundamental, Version 3
Basic design of idea: https://www.youtube.com/watch?v=WjTrfoiB0MQ
Specification:
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2020/n4873.html#scopeguard
Type of Change
Test Plan
Unit tests added.