[v3,02/17] Introduce scope_exit

Message ID 20190123152131.29893-3-palves@redhat.com
State New, archived
Headers

Commit Message

Pedro Alves Jan. 23, 2019, 3:21 p.m. UTC
  This add a new template class scope_exit.  scope_exit is a
general-purpose scope guard that calls its exit function at the end of
the current scope.  A scope_exit may be canceled by calling the
"release" method.  The API is modeled on P0052R5 - Generic Scope Guard
and RAII Wrapper for the Standard Library, which is itself based on
Andrej Alexandrescu's ScopeGuard/SCOPE_EXIT.

The main advantage of scope_exit is avoiding writing single-use RAII
classes and its boilerplate.  Following patches will remove a few of
such classes.

There are two forms available:

 - The "make_scope_exit" form allows canceling the scope guard.  Use
   it like this:

     auto cleanup = make_scope_exit ( <function, function object, lambda> );
     ...
     cleanup.release (); // cancel

 - If you don't need to cancel the guard, you can use the SCOPE_EXIT
   macro, like this:

     SCOPE_EXIT { /* any code you like here. */ }

Note: scope_exit instances do not allocate anything on the heap.

gdb/ChangeLog:
yyyy-mm-dd  Pedro Alves  <palves@redhat.com>
	    Andrew Burgess  <andrew.burgess@embecosm.com>
	    Tom Tromey  <tom@tromey.com>

	* common/scope-exit.h: New file.
---
 gdb/common/scope-exit.h | 186 ++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 186 insertions(+)
 create mode 100644 gdb/common/scope-exit.h
  

Patch

diff --git a/gdb/common/scope-exit.h b/gdb/common/scope-exit.h
new file mode 100644
index 0000000000..8cdbec305a
--- /dev/null
+++ b/gdb/common/scope-exit.h
@@ -0,0 +1,186 @@ 
+/* Copyright (C) 2019 Free Software Foundation, Inc.
+
+   This file is part of GDB.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
+
+#ifndef COMMON_SCOPE_EXIT_H
+#define COMMON_SCOPE_EXIT_H
+
+#include <functional>
+#include <type_traits>
+#include "common/preprocessor.h"
+
+/* scope_exit is a general-purpose scope guard that calls its exit
+   function at the end of the current scope.  A scope_exit may be
+   canceled by calling the "release" method.  The API is modeled on
+   P0052R5 - Generic Scope Guard and RAII Wrapper for the Standard
+   Library, which is itself based on Andrej Alexandrescu's
+   ScopeGuard/SCOPE_EXIT.
+
+   There are two forms available:
+
+   - The "make_scope_exit" form allows canceling the scope guard.  Use
+     it like this:
+
+     auto cleanup = make_scope_exit ( <function, function object, lambda> );
+     ...
+     cleanup.release (); // cancel
+
+   - If you don't need to cancel the guard, you can use the SCOPE_EXIT
+     macro, like this:
+
+     SCOPE_EXIT
+       {
+	 // any code you like here.
+       }
+
+   See also forward_scope_exit.
+*/
+
+/* CRTP base class for cancelable scope_exit-like classes.  Implements
+   the common call-custom-function-from-dtor functionality.  Classes
+   that inherit this implement the on_exit() method, which is called
+   from scope_exit_base's dtor.  */
+
+template <typename CRTP>
+class scope_exit_base
+{
+public:
+  scope_exit_base () = default;
+
+  ~scope_exit_base ()
+  {
+    if (!m_released)
+      {
+	auto *self = static_cast<CRTP *> (this);
+	self->on_exit ();
+      }
+  }
+
+  /* This is needed for make_scope_exit because copy elision isn't
+     guaranteed until C++17.  An optimizing compiler will usually skip
+     calling this, but it must exist.  */
+  scope_exit_base (const scope_exit_base &other)
+    : m_released (other.m_released)
+  {
+    other.m_released = true;
+  }
+
+  void operator= (const scope_exit_base &) = delete;
+
+  /* If this is called, then the wrapped function will not be called
+     on destruction.  */
+  void release () noexcept
+  {
+    m_released = true;
+  }
+
+private:
+
+  /* True if released.  Mutable because of the copy ctor hack
+     above.  */
+  mutable bool m_released = false;
+};
+
+/* The scope_exit class.  */
+
+template<typename EF>
+class scope_exit : public scope_exit_base<scope_exit<EF>>
+{
+  /* For access to on_exit().  */
+  friend scope_exit_base<scope_exit<EF>>;
+
+public:
+
+  template<typename EFP,
+	   typename = gdb::Requires<std::is_constructible<EF, EFP>>>
+  scope_exit (EFP &&f)
+    try : m_exit_function ((!std::is_lvalue_reference<EFP>::value
+			    && std::is_nothrow_constructible<EF, EFP>::value)
+			   ? std::move (f)
+			   : f)
+  {
+  }
+  catch (...)
+    {
+      /* "If the initialization of exit_function throws an exception,
+	 calls f()."  */
+      f ();
+    }
+
+  template<typename EFP,
+	   typename = gdb::Requires<std::is_constructible<EF, EFP>>>
+  scope_exit (scope_exit &&rhs)
+    noexcept (std::is_nothrow_move_constructible<EF>::value
+	      || std::is_nothrow_copy_constructible<EF>::value)
+    : m_exit_function (std::is_nothrow_constructible<EFP>::value
+		       ? std::move (rhs)
+		       : rhs)
+  {
+    rhs.release ();
+  }
+
+  /* This is needed for make_scope_exit because copy elision isn't
+     guaranteed until C++17.  An optimizing compiler will usually skip
+     calling this, but it must exist.  */
+  scope_exit (const scope_exit &other)
+    : scope_exit_base<scope_exit<EF>> (other),
+      m_exit_function (other.m_exit_function)
+  {
+  }
+
+  void operator= (const scope_exit &) = delete;
+  void operator= (scope_exit &&) = delete;
+
+private:
+  void on_exit ()
+  {
+    m_exit_function ();
+  }
+
+  /* The function to call on scope exit.  */
+  EF m_exit_function;
+};
+
+template <typename EF>
+scope_exit<typename std::decay<EF>::type>
+make_scope_exit (EF &&f)
+{
+  return scope_exit<typename std::decay<EF>::type> (std::forward<EF> (f));
+}
+
+namespace detail
+{
+
+enum class scope_exit_lhs {};
+
+template<typename EF>
+scope_exit<typename std::decay<EF>::type>
+operator+ (scope_exit_lhs, EF &&rhs)
+{
+  return scope_exit<typename std::decay<EF>::type> (std::forward<EF> (rhs));
+}
+
+}
+
+/* Register a block of code to run on scope exit.  Note that the local
+   context is captured by reference, which means you should be careful
+   to avoid inadvertently changing a captured local's value before the
+   scope exit runs.  */
+
+#define SCOPE_EXIT \
+  auto CONCAT(scope_exit_, __LINE__) = ::detail::scope_exit_lhs () + [&] ()
+
+#endif /* COMMON_SCOPE_EXIT_H */