diff --git a/libcxx/include/CMakeLists.txt b/libcxx/include/CMakeLists.txt --- a/libcxx/include/CMakeLists.txt +++ b/libcxx/include/CMakeLists.txt @@ -357,6 +357,7 @@ __utility/rel_ops.h __utility/swap.h __utility/to_underlying.h + __utility/transaction.h __variant/monostate.h algorithm any diff --git a/libcxx/include/__utility/transaction.h b/libcxx/include/__utility/transaction.h new file mode 100644 --- /dev/null +++ b/libcxx/include/__utility/transaction.h @@ -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 + +#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 +// Iterator uninitialized_copy_n(Iterator iter, Size n, OutputIterator out) { +// typedef typename iterator_traits::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 +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 diff --git a/libcxx/include/module.modulemap b/libcxx/include/module.modulemap --- a/libcxx/include/module.modulemap +++ b/libcxx/include/module.modulemap @@ -915,6 +915,7 @@ 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 { diff --git a/libcxx/include/utility b/libcxx/include/utility --- a/libcxx/include/utility +++ b/libcxx/include/utility @@ -231,6 +231,7 @@ #include <__utility/rel_ops.h> #include <__utility/swap.h> #include <__utility/to_underlying.h> +#include <__utility/transaction.h> #include #include #include diff --git a/libcxx/test/libcxx/diagnostics/detail.headers/utility/transaction.module.verify.cpp b/libcxx/test/libcxx/diagnostics/detail.headers/utility/transaction.module.verify.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/libcxx/diagnostics/detail.headers/utility/transaction.module.verify.cpp @@ -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> diff --git a/libcxx/test/libcxx/utilities/transaction.pass.cpp b/libcxx/test/libcxx/utilities/transaction.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/libcxx/utilities/transaction.pass.cpp @@ -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 // for __transaction +#include +#include +#include + +#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 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 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 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 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; + + static_assert(!std::is_default_constructible::value, ""); + + static_assert(!std::is_copy_constructible::value, ""); + static_assert( std::is_move_constructible::value, ""); + + static_assert(!std::is_copy_assignable::value, ""); + static_assert(!std::is_move_assignable::value, ""); + + // Check noexcept-ness of a few operations + { + struct ThrowOnMove { + ThrowOnMove(ThrowOnMove&&) noexcept(false) { } + void operator()() const { } + }; + using ThrowOnMoveTransaction = std::__transaction; + + ASSERT_NOEXCEPT(std::declval().__complete()); + static_assert( std::is_nothrow_move_constructible::value, ""); + static_assert(!std::is_nothrow_move_constructible::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 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 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 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; +}