Deferral is a modern C++ library that provides a scope-exit API, inspired by the defer
statement found in languages like Go and Swift, as well as ScopeGurad and scope_exit
TR v3. It enables developers to ensure that specific blocks of code are executed when the current scope ends, either successfully or due to an error, enhancing the management of resource cleanup and exception safety.
While there are numerous implementations already available, Deferral aims to include many of the unique features from other scope guard packages. It also includes a few minor tweeks to the implementation to improve performance. Finally, it provides a defer
macro to match the syntax of Go and Swift.
Scope guard / scope exit has been inplemented by many times. See the links below for various implementations.
Deferral's only requirement is a C++11 compatible compiler.
To integrate Deferral into your C++ projects, may choose include the header files from the /include
directory in your project. Ensure your compiler supports C++11 or later, as this library utilizes modern C++ features and deduction guides for template type inference.
- Copy the deferral.hh file into your project.
- Add the Deferral repo as a submodual or subtree to your project.
- CMake
find_project(deferral)
, if deferral is installed on the system
Deferral offers three main components: DeferExit
, DeferFail
, and DeferSuccess
. Each of these can be used to execute code at the end of a scope depending on the exit condition:
- DeferExit: Executes code unconditionally at the end of a scope.
- DeferFail: Executes code only if the scope exits due to an exception.
- DeferSuccess: Executes code only if the scope exits without throwing any exception.
You can also use the defer
, defer_fail
, and defer_success
macros for ease of use, which mimic the defer
keyword from Go and Swift.
Deferral provides four different interfaces:
- the macros
- factory functions
- class constructor (C++17 and later)
The macros provide the most consise API and is recommended interface. The macros closely resemple
the syntax of
defer
in other langages and keeps the code clean.
The simplest usage of Deferral is to unconditionally execute a block of code on scope exit. Below are examples of using each of Deferral interfaces to automatically close an opened file.
-
defer
keyword-like macro#include "deferral.hh" void exampleFunction() { FILE* file = fopen("example.txt", "w"); if (!file) return; defer { fclose(file); }; // Ensure file is closed at the end of the scope // Your code here, e.g., writing to the file }
-
DEFER
macro#include "deferral.hh" void exampleFunction() { FILE* file = fopen("example.txt", "w"); if (!file) return; DEFER { fclose(file); }; // Ensure file is closed at the end of the scope // Your code here, e.g., writing to the file }
-
make_defer_exit
factory function#include "deferral.hh" void exampleFunction() { FILE* file = fopen("example.txt", "w"); if (!file) return; auto d = deferral::make_defer_exit{[&]() { fclose(file); }}; // Ensure file is closed at the end of the scope // Your code here, e.g., writing to the file }
-
DeferExit
class with template deduction guide (C++17 only)#include "deferral.hh" void exampleFunction() { FILE* file = fopen("example.txt", "w"); if (!file) return; auto d = deferral::DeferExit{[&]() { fclose(file); }}; // Ensure file is closed at the end of the scope // Your code here, e.g., writing to the file }
Deferral includes success and failure scope guards that conditionally execute the deferred function
if an exception is or is not thrown respectively. That is, defer_success
operation are executed
iif the the scope exits without an exception being thrown. defer_fail
is the opposite, in a sense,
to defer_success
, where the operation is only executed when the scope exits due to an unhandled
exception.
#include "deferral.hh"
void conditionalExample() {
try {
defer_success { std::cout << "No exceptions thrown.\n"; };
defer_fail { std::cout << "An exception was thrown.\n"; };
// Code that might throw ...
} catch (...) {
// fail deferral IS executed on throw ...
// success deferral is NOT executed on throw ...
throw;
}
// Success deferral is executed on normal return,
// and failure deferral is NOT executed
}
Deferral provides the release()
function to disable the deferred operation. To use this
feature, the deferral object must have a named variable that can be referrenced in the function.
This can be done using the following:
defer_n(x)
,DEFER_N(x)
,deferral::make_defer_exit()
,DeferExit{}
defer_success_n(x)
,DEFER_SUCCESS_N(x)
,deferral::make_defer_exit()
,DeferExit{}
defer_fail_n(x)
,DEFER_FAIL_N(x)
,deferral::make_defer_fail()
,DeferFail{}
The defer_(success_|fail_|)_n
and DEFER_(SUCCESS_|FAIL_|)_N
marcros create a variable with the
name give agrument.
#include "deferral.hh"
std::byte* exampleFunction(int n) {
std::byte* buf = new std::byte[n];
// Ensure buf is cleaned up on early exit
defer_n(d) { delete [] buf; }; // Create variable `d`.
// Perform some initialization that might fail.
d.release(); // buf is not deleted after this point
return buf;
}
The factory functions can be used to achive the same result.
deferral::make_defer_exit()
deferral::make_defer_fail()
deferral::make_defer_success()
#include "deferral.hh"
std::byte* exampleFunction(int n) {
std::byte* buf = new std::byte[n];
// Ensure buf is cleaned up on early exit
auto d = deferral::make_defer_exit([&]() { delete [] buf; });
// Perform some initialization that might fail.
d.release(); // buf is not deleted after this point
return buf;
}
If you are using the c++17 standard, or later, you can use the class classes directly with the
#include "deferral.hh"
std::byte* exampleFunction(int n) {
std::byte* buf = new std::byte[n];
// Ensure buf is cleaned up on early exit
auto d = deferral::DeferExit{[&]() { delete [] buf; }}; // C++17 or later
// Perform some initialization that might fail.
d.release(); // buf is not deleted after this point
return buf;
}
You can disable macro genration in the code using the following macro definitions either as command line arguments or in-file macro definitions.
- Compiler command
$ c++ -DDEFERRAL_NO_MACROS=1 ...
- In file
#define DEFERRAL_NO_MACROS 1 #include <deferral.hh>
- Compiler command
$ c++ -DDEFERRAL_NO_KEYWORDS=1 ...
- In file
#define DEFERRAL_NO_KEYWORDS 1 #include <deferral.hh>
namespace deferral {
template <typename EF>
class DeferExit {
template <typename F>
explicit DeferExit(F&& f) noexcept(...);
DeferExit(DeferExit&& other) noexcept(...);
DeferExit(const DeferExit&) = delete;
~DeferExit() noexcept(...);
DeferExit& operator=(const DeferExit&) = delete;
DeferExit& operator=(DeferExit&&) = delete;
void release() noexcept;
}; // class DeferExit
template <typename EF>
class DeferFail {
template <typename F>
explicit DeferFail(F&& f) noexcept(...);
DeferFail(DeferFail&& other) noexcept(...);
DeferFail(const DeferFail&) = delete;
~DeferFail() noexcept;
DeferFail& operator=(const DeferFail&) = delete;
DeferFail& operator=(DeferFail&&) = delete;
void release() noexcept;
}; // class DeferFail
template <typename EF>
class DeferSuccess {
template <typename F>
explicit DeferSuccess(F&& f) noexcept(...);
DeferSuccess(DeferSuccess&& other) noexcept(...);
DeferSuccess(const DeferSuccess&) = delete;
~DeferSuccess() noexcept;
DeferSuccess& operator=(const DeferSuccess&) = delete;
DeferSuccess& operator=(DeferSuccess&&) = delete;
void release() noexcept;
}; // class DeferSuccess
// Deduction guide, C++17 only.
#if __cplusplus >= 201703L
template <typename funcT>
DeferExit(funcT) -> DeferExit<funcT>;
template <typename funcT>
DeferFail(funcT) -> DeferFail<funcT>;
template <typename funcT>
DeferSuccess(funcT) -> DeferSuccess<funcT>;
#endif
template <typename funcT>
DEFERRAL_VISIBILITY_HIDDEN inline DeferExit<typename std::decay<funcT>::type>
make_defer_exit(funcT&& f) noexcept(...);
template <typename funcT>
inline DeferFail<typename std::decay<funcT>::type>
make_defer_fail(funcT&& f) noexcept(...);
template <typename funcT>
inline DeferSuccess<typename std::decay<funcT>::type>
make_defer_success(funcT&& f) noexcept(...);
} // namespace deferral
#if !defined(DEFERRAL_NO_MACROS)
// Macros defining the primary API
#define DEFER ...
#define DEFER_N(x) ...
#define DEFER_FAIL ...
#define DEFER_FAIL_N(x) ...
#define DEFER_SUCCESS ...
#define DEFER_SUCCESS(x) ...
#if !defined(DEFERRAL_NO_KEYWORDS)
// Define macros that simulate key words
#define defer_n(x) DEFER_N(x)
#define defer DEFER
#define defer_fail_n(x) DEFER_FAIL_N(x)
#define defer_fail DEFER_FAIL
#define defer_success_n(x) DEFER_SUCCESS_N(x)
#define defer_success DEFER_SUCCESS
#endif
#endif
We welcome contributions to the Deferral library! Whether it's bug reports, feature requests, or pull requests, your help is valuable to us. Please feel free to contribute to making this library even better.
This library is distributed under the MIT License. Feel free to use it in your projects as you see fit.
MIT License
Copyright (c) [year] [fullname]
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.