[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