From: Wolfgang Bangerth Date: Fri, 5 May 2023 20:37:15 +0000 (-0600) Subject: Add a ScopeExit function. X-Git-Tag: v9.5.0-rc1~253^2~4 X-Git-Url: https://gitweb.dealii.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=c7f2aea0931031d61ad95a8b6193e4bf24ba63c8;p=dealii.git Add a ScopeExit function. --- diff --git a/include/deal.II/base/scope_exit.h b/include/deal.II/base/scope_exit.h new file mode 100644 index 0000000000..202fb28529 --- /dev/null +++ b/include/deal.II/base/scope_exit.h @@ -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 + +#include + + +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::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(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 &exit_function); + + /** + * Destructor. Execute the stored action. + */ + ~ScopeExit(); + +private: + /** + * A copy of the function to be executed. + */ + const std::function exit_function; +}; + + + +inline ScopeExit::ScopeExit(const std::function &exit_function) + : exit_function(exit_function) +{} + + + +inline ScopeExit::~ScopeExit() +{ + // Actually trigger the stored function + exit_function(); +} + + +DEAL_II_NAMESPACE_CLOSE + +#endif