forked from OSchip/llvm-project
[libc++] Add a helper class to write code with the strong exception guarantee
__transaction is a helper class that allows rolling back code in case an exception is thrown. The main goal is to reduce the clutter when code needs to be guarded with `#if _LIBCPP_NO_EXCEPTIONS`. Differential Revision: https://reviews.llvm.org/D115730
This commit is contained in:
parent
e1600db19d
commit
37e6bd8bc8
|
@ -358,6 +358,7 @@ set(files
|
|||
__utility/rel_ops.h
|
||||
__utility/swap.h
|
||||
__utility/to_underlying.h
|
||||
__utility/transaction.h
|
||||
__variant/monostate.h
|
||||
algorithm
|
||||
any
|
||||
|
|
|
@ -0,0 +1,91 @@
|
|||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
||||
// See https://llvm.org/LICENSE.txt for license information.
|
||||
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#ifndef _LIBCPP___UTILITY_TRANSACTION_H
|
||||
#define _LIBCPP___UTILITY_TRANSACTION_H
|
||||
|
||||
#include <__config>
|
||||
#include <__utility/exchange.h>
|
||||
#include <__utility/move.h>
|
||||
#include <type_traits>
|
||||
|
||||
#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
|
||||
#pragma GCC system_header
|
||||
#endif
|
||||
|
||||
_LIBCPP_BEGIN_NAMESPACE_STD
|
||||
|
||||
// __transaction is a helper class for writing code with the strong exception guarantee.
|
||||
//
|
||||
// When writing code that can throw an exception, one can store rollback instructions in a
|
||||
// transaction so that if an exception is thrown at any point during the lifetime of the
|
||||
// transaction, it will be rolled back automatically. When the transaction is done, one
|
||||
// must mark it as being complete so it isn't rolled back when the transaction is destroyed.
|
||||
//
|
||||
// Transactions are not default constructible, they can't be copied or assigned to, but
|
||||
// they can be moved around for convenience.
|
||||
//
|
||||
// __transaction can help greatly simplify code that would normally be cluttered by
|
||||
// `#if _LIBCPP_NO_EXCEPTIONS`. For example:
|
||||
//
|
||||
// template <class Iterator, class Size, class OutputIterator>
|
||||
// Iterator uninitialized_copy_n(Iterator iter, Size n, OutputIterator out) {
|
||||
// typedef typename iterator_traits<Iterator>::value_type value_type;
|
||||
// __transaction transaction([start=out, &out] {
|
||||
// std::destroy(start, out);
|
||||
// });
|
||||
//
|
||||
// for (; n > 0; ++iter, ++out, --n) {
|
||||
// ::new ((void*)std::addressof(*out)) value_type(*iter);
|
||||
// }
|
||||
// transaction.__complete();
|
||||
// return out;
|
||||
// }
|
||||
//
|
||||
template <class _Rollback>
|
||||
struct __transaction {
|
||||
__transaction() = delete;
|
||||
|
||||
_LIBCPP_HIDE_FROM_ABI
|
||||
_LIBCPP_CONSTEXPR_AFTER_CXX17 explicit __transaction(_Rollback __rollback)
|
||||
: __rollback_(_VSTD::move(__rollback))
|
||||
, __completed_(false)
|
||||
{ }
|
||||
|
||||
_LIBCPP_HIDE_FROM_ABI
|
||||
_LIBCPP_CONSTEXPR_AFTER_CXX17 __transaction(__transaction&& __other)
|
||||
_NOEXCEPT_(is_nothrow_move_constructible<_Rollback>::value)
|
||||
: __rollback_(_VSTD::move(__other.__rollback_))
|
||||
, __completed_(__other.__completed_)
|
||||
{
|
||||
__other.__completed_ = true;
|
||||
}
|
||||
|
||||
__transaction(__transaction const&) = delete;
|
||||
__transaction& operator=(__transaction const&) = delete;
|
||||
__transaction& operator=(__transaction&&) = delete;
|
||||
|
||||
_LIBCPP_HIDE_FROM_ABI
|
||||
_LIBCPP_CONSTEXPR_AFTER_CXX17 void __complete() _NOEXCEPT {
|
||||
__completed_ = true;
|
||||
}
|
||||
|
||||
_LIBCPP_HIDE_FROM_ABI
|
||||
_LIBCPP_CONSTEXPR_AFTER_CXX17 ~__transaction() {
|
||||
if (!__completed_)
|
||||
__rollback_();
|
||||
}
|
||||
|
||||
private:
|
||||
_Rollback __rollback_;
|
||||
bool __completed_;
|
||||
};
|
||||
|
||||
_LIBCPP_END_NAMESPACE_STD
|
||||
|
||||
#endif // _LIBCPP___UTILITY_TRANSACTION_H
|
|
@ -916,6 +916,7 @@ module std [system] {
|
|||
module rel_ops { private header "__utility/rel_ops.h" }
|
||||
module swap { private header "__utility/swap.h" }
|
||||
module to_underlying { private header "__utility/to_underlying.h" }
|
||||
module transaction { private header "__utility/transaction.h" }
|
||||
}
|
||||
}
|
||||
module valarray {
|
||||
|
|
|
@ -231,6 +231,7 @@ template <class T>
|
|||
#include <__utility/rel_ops.h>
|
||||
#include <__utility/swap.h>
|
||||
#include <__utility/to_underlying.h>
|
||||
#include <__utility/transaction.h>
|
||||
#include <compare>
|
||||
#include <initializer_list>
|
||||
#include <version>
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
||||
// See https://llvm.org/LICENSE.txt for license information.
|
||||
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
// REQUIRES: modules-build
|
||||
|
||||
// WARNING: This test was generated by 'generate_private_header_tests.py'
|
||||
// and should not be edited manually.
|
||||
|
||||
// expected-error@*:* {{use of private header from outside its module: '__utility/transaction.h'}}
|
||||
#include <__utility/transaction.h>
|
|
@ -0,0 +1,159 @@
|
|||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
||||
// See https://llvm.org/LICENSE.txt for license information.
|
||||
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
// UNSUPPORTED: c++03
|
||||
|
||||
#include <utility> // for __transaction
|
||||
#include <cassert>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
#include "test_macros.h"
|
||||
|
||||
TEST_CONSTEXPR_CXX20 bool test() {
|
||||
// Make sure the transaction is rolled back if it is not marked as complete when
|
||||
// it goes out of scope.
|
||||
{
|
||||
bool rolled_back = false;
|
||||
{
|
||||
auto rollback = [&] { rolled_back = true; };
|
||||
std::__transaction<decltype(rollback)> t(rollback);
|
||||
}
|
||||
assert(rolled_back);
|
||||
}
|
||||
|
||||
// Make sure the transaction is not rolled back if it is marked as complete when
|
||||
// it goes out of scope.
|
||||
{
|
||||
bool rolled_back = false;
|
||||
{
|
||||
auto rollback = [&] { rolled_back = true; };
|
||||
std::__transaction<decltype(rollback)> t(rollback);
|
||||
t.__complete();
|
||||
}
|
||||
assert(!rolled_back);
|
||||
}
|
||||
|
||||
// Make sure that we will perform the right number of rollbacks when a transaction has
|
||||
// been moved around
|
||||
{
|
||||
// When we don't complete it (exactly 1 rollback should happen)
|
||||
{
|
||||
int rollbacks = 0;
|
||||
{
|
||||
auto rollback = [&] { ++rollbacks; };
|
||||
std::__transaction<decltype(rollback)> t(rollback);
|
||||
auto other = std::move(t);
|
||||
}
|
||||
assert(rollbacks == 1);
|
||||
}
|
||||
|
||||
// When we do complete it (no rollbacks should happen)
|
||||
{
|
||||
int rollbacks = 0;
|
||||
{
|
||||
auto rollback = [&] { ++rollbacks; };
|
||||
std::__transaction<decltype(rollback)> t(rollback);
|
||||
auto other = std::move(t);
|
||||
other.__complete();
|
||||
}
|
||||
assert(rollbacks == 0);
|
||||
}
|
||||
}
|
||||
|
||||
// Basic properties of the type
|
||||
{
|
||||
struct Rollback { void operator()() const { } };
|
||||
using Transaction = std::__transaction<Rollback>;
|
||||
|
||||
static_assert(!std::is_default_constructible<Transaction>::value, "");
|
||||
|
||||
static_assert(!std::is_copy_constructible<Transaction>::value, "");
|
||||
static_assert( std::is_move_constructible<Transaction>::value, "");
|
||||
|
||||
static_assert(!std::is_copy_assignable<Transaction>::value, "");
|
||||
static_assert(!std::is_move_assignable<Transaction>::value, "");
|
||||
|
||||
// Check noexcept-ness of a few operations
|
||||
{
|
||||
struct ThrowOnMove {
|
||||
ThrowOnMove(ThrowOnMove&&) noexcept(false) { }
|
||||
void operator()() const { }
|
||||
};
|
||||
using ThrowOnMoveTransaction = std::__transaction<ThrowOnMove>;
|
||||
|
||||
ASSERT_NOEXCEPT(std::declval<Transaction>().__complete());
|
||||
static_assert( std::is_nothrow_move_constructible<Transaction>::value, "");
|
||||
static_assert(!std::is_nothrow_move_constructible<ThrowOnMoveTransaction>::value, "");
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void test_exceptions() {
|
||||
#ifndef TEST_HAS_NO_EXCEPTIONS
|
||||
// Make sure the rollback is performed when an exception is thrown during the
|
||||
// lifetime of the transaction.
|
||||
{
|
||||
bool rolled_back = false;
|
||||
auto rollback = [&] { rolled_back = true; };
|
||||
try {
|
||||
std::__transaction<decltype(rollback)> t(rollback);
|
||||
throw 0;
|
||||
} catch (...) { }
|
||||
assert(rolled_back);
|
||||
}
|
||||
|
||||
// Make sure we don't roll back if an exception is thrown but the transaction
|
||||
// has been marked as complete when that happens.
|
||||
{
|
||||
bool rolled_back = false;
|
||||
auto rollback = [&] { rolled_back = true; };
|
||||
try {
|
||||
std::__transaction<decltype(rollback)> t(rollback);
|
||||
t.__complete();
|
||||
throw 0;
|
||||
} catch (...) { }
|
||||
assert(!rolled_back);
|
||||
}
|
||||
|
||||
// Make sure __transaction does not rollback if the transaction is marked as
|
||||
// completed within a destructor.
|
||||
{
|
||||
struct S {
|
||||
explicit S(bool& x) : x_(x) { }
|
||||
|
||||
~S() {
|
||||
auto rollback = [this]{ x_ = true; };
|
||||
std::__transaction<decltype(rollback)> t(rollback);
|
||||
t.__complete();
|
||||
}
|
||||
|
||||
bool& x_;
|
||||
};
|
||||
|
||||
bool rolled_back = false;
|
||||
try {
|
||||
S s(rolled_back);
|
||||
throw 0;
|
||||
} catch (...) {
|
||||
assert(!rolled_back);
|
||||
}
|
||||
}
|
||||
#endif // TEST_HAS_NO_EXCEPTIONS
|
||||
}
|
||||
|
||||
int main(int, char**) {
|
||||
test();
|
||||
test_exceptions();
|
||||
#if TEST_STD_VER > 17
|
||||
static_assert(test(), "");
|
||||
#endif
|
||||
return 0;
|
||||
}
|
Loading…
Reference in New Issue