]> https://gitweb.dealii.org/ - dealii.git/commitdiff
Add a ScopeExit function.
authorWolfgang Bangerth <bangerth@colostate.edu>
Fri, 5 May 2023 20:37:15 +0000 (14:37 -0600)
committerWolfgang Bangerth <bangerth@colostate.edu>
Sun, 7 May 2023 17:13:25 +0000 (11:13 -0600)
include/deal.II/base/scope_exit.h [new file with mode: 0644]

diff --git a/include/deal.II/base/scope_exit.h b/include/deal.II/base/scope_exit.h
new file mode 100644 (file)
index 0000000..202fb28
--- /dev/null
@@ -0,0 +1,172 @@
+// ---------------------------------------------------------------------
+//
+// Copyright (C) 2023 by the deal.II authors
+//
+// This file is part of the deal.II library.
+//
+// The deal.II library is free software; you can use it, redistribute
+// it, and/or modify it under the terms of the GNU Lesser General
+// Public License as published by the Free Software Foundation; either
+// version 2.1 of the License, or (at your option) any later version.
+// The full text of the license can be found in the file LICENSE.md at
+// the top level directory of deal.II.
+//
+// ---------------------------------------------------------------------
+
+#ifndef dealii_scope_exit_h
+#define dealii_scope_exit_h
+
+#include <deal.II/base/config.h>
+
+#include <functional>
+
+
+DEAL_II_NAMESPACE_OPEN
+
+/**
+ * A class that stores a function object (typically a lambda function)
+ * that is executed in the destructor of the current object. Such
+ * a class is useful to execute an action *whenever* the function in
+ * which the object is declared exits, whether that is by falling
+ * off the end of the function, an explicit `return` statement, or
+ * because an exception is thrown. In all of these cases, the
+ * destructors of all local objects are run, and the destructor
+ * of the ScopeExit object then executes the stored action.
+ *
+ * The typical use case is that we want to perform a clean-up
+ * action. For example, let's say that we want to print to the
+ * console that we're leaving a long-running and potentially
+ * complicated function. This could be achieved as follows:
+ * @code
+ * void long_running_and_complex_function ()
+ * {
+ *   ScopeExit scope_exit_action ([]() { std::cout << "Leaving the long
+ * running function!\n"; });
+ *
+ *   for (...)
+ *   {
+ *     // some 800 lines of complex code
+ *   }
+ * @endcode
+ * The point here is that in order to have the message printed, we do not
+ * need to go through the 800 lines of complex code and find all places
+ * where either there is an explicit `return` statement, or where a
+ * function that is called may throw an exception that we do not explicitly
+ * catch: *Any* way the current function is exited will lead to the
+ * message being printed.
+ *
+ * A perhaps more instructive example is the following (from some past
+ * iteration of the implementation of SUNDIALS::KINSOL::solve()):
+ * @code
+ *    status = KINSol(kinsol_mem, solution, data.strategy, u_scale, f_scale);
+ *
+ *    if (pending_exception)
+ *      {
+ *        try
+ *          {
+ *            std::rethrow_exception(pending_exception);
+ *          }
+ *        catch (const RecoverableUserCallbackError &exc)
+ *          {
+ *            pending_exception = nullptr;
+ *            if (status == 0)
+ *              ; // just eat the exception
+ *            else
+ *              throw;
+ *          }
+ *        catch (...)
+ *          {
+ *            pending_exception = nullptr;
+ *            throw;
+ *          }
+ *      }
+ *    AssertKINSOL(status);
+ *
+ *    internal::copy(initial_guess_and_solution, solution);
+ *    // Free the vectors which are no longer used.
+ *#  ifdef DEAL_II_WITH_MPI
+ *    if (is_serial_vector<VectorType>::value == false)
+ *      {
+ *        N_VDestroy_Parallel(solution);
+ *        N_VDestroy_Parallel(u_scale);
+ *        N_VDestroy_Parallel(f_scale);
+ *      }
+ *    else
+ *#  endif
+ *      {
+ *        N_VDestroy_Serial(solution);
+ *        N_VDestroy_Serial(u_scale);
+ *        N_VDestroy_Serial(f_scale);
+ *      }
+ *    long nniters;
+ *    status = KINGetNumNonlinSolvIters(kinsol_mem, &nniters);
+ *    AssertKINSOL(status);
+ *    if (J != nullptr)
+ *      SUNMatDestroy(J);
+ *    if (LS != nullptr)
+ *      SUNLinSolFree(LS);
+ *    KINFree(&kinsol_mem);
+ *    return static_cast<unsigned int>(nniters);
+ * @endcode
+ *
+ * This code has a bug: At the bottom, it contains clean-up code
+ * that destroys a variety of objects created throughout the life of
+ * the function we are looking at. Because SUNDIALS is a library
+ * written in C, objects such as `solution`, `u_scale`, `J`, `LS`,
+ * etc., are really just regular pointers that need to be explicitly
+ * freed. But this does not happen in the places higher up where we
+ * `throw` an exception and don't catch it again immediately. One
+ * might be tempted to just copy the clean-up code to right before the
+ * `std::rethrow_exception` statement, but there is a path through the
+ * `catch` clauses marked with "just eat the exception" that ends up
+ * returning control flow back to the regular path, and if we hit this
+ * path, we would call the clean-up functions *twice* -- also not what
+ * we want. The upshot of this kind of example is that there are cases
+ * where it is quite difficult to say where exactly a function will
+ * exit and where the clean-up code needs to be placed; it is far
+ * easier to write it only once and let the compiler determine where
+ * it needs to be called, via a class such as the current one.
+ *
+ * The function is conceptually very similar to the
+ * [std::experimental::scope_exit](https://en.cppreference.com/w/cpp/experimental/scope_exit)
+ * class template that may find its way into a future C++ standard.
+ */
+class ScopeExit
+{
+public:
+  /**
+   * Constructor. Takes a function object that is to be executed at the
+   * place where the current object goes out of scope as argument.
+   */
+  ScopeExit(const std::function<void()> &exit_function);
+
+  /**
+   * Destructor. Execute the stored action.
+   */
+  ~ScopeExit();
+
+private:
+  /**
+   * A copy of the function to be executed.
+   */
+  const std::function<void()> exit_function;
+};
+
+
+
+inline ScopeExit::ScopeExit(const std::function<void()> &exit_function)
+  : exit_function(exit_function)
+{}
+
+
+
+inline ScopeExit::~ScopeExit()
+{
+  // Actually trigger the stored function
+  exit_function();
+}
+
+
+DEAL_II_NAMESPACE_CLOSE
+
+#endif

In the beginning the Universe was created. This has made a lot of people very angry and has been widely regarded as a bad move.

Douglas Adams


Typeset in Trocchi and Trocchi Bold Sans Serif.