[libcxx-commits] [libcxx] 37e6bd8 - [libc++] Add a helper class to write code with the strong exception guarantee
Louis Dionne via libcxx-commits
libcxx-commits at lists.llvm.org
Mon Dec 20 08:17:42 PST 2021
Author: Louis Dionne
Date: 2021-12-20T11:17:29-05:00
New Revision: 37e6bd8bc8da29ad485547a683c6685254d4011d
URL: https://github.com/llvm/llvm-project/commit/37e6bd8bc8da29ad485547a683c6685254d4011d
DIFF: https://github.com/llvm/llvm-project/commit/37e6bd8bc8da29ad485547a683c6685254d4011d.diff
LOG: [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
Added:
libcxx/include/__utility/transaction.h
libcxx/test/libcxx/diagnostics/detail.headers/utility/transaction.module.verify.cpp
libcxx/test/libcxx/utilities/transaction.pass.cpp
Modified:
libcxx/include/CMakeLists.txt
libcxx/include/module.modulemap
libcxx/include/utility
Removed:
################################################################################
diff --git a/libcxx/include/CMakeLists.txt b/libcxx/include/CMakeLists.txt
index e0801c7ee5226..f7e459b59179d 100644
--- a/libcxx/include/CMakeLists.txt
+++ b/libcxx/include/CMakeLists.txt
@@ -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
diff --git a/libcxx/include/__utility/transaction.h b/libcxx/include/__utility/transaction.h
new file mode 100644
index 0000000000000..5bc3a500fdc55
--- /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 <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
diff --git a/libcxx/include/module.modulemap b/libcxx/include/module.modulemap
index 12e0a15f52f58..fa4170ba1ed32 100644
--- a/libcxx/include/module.modulemap
+++ b/libcxx/include/module.modulemap
@@ -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 {
diff --git a/libcxx/include/utility b/libcxx/include/utility
index ac8470e269d0c..9ab7b8e0ffb34 100644
--- a/libcxx/include/utility
+++ b/libcxx/include/utility
@@ -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>
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
index 0000000000000..d453fa52f7320
--- /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
index 0000000000000..e517ffdc9e9f5
--- /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 <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;
+}
More information about the libcxx-commits
mailing list