What is standard defer/finalizer implementation in C++?
This implementation is zero-overhead unlike some other answers, as well as syntactically nicer and easier to use. It also has zero dependencies, reducing compile times.
You can paste this snippet anywhere in your codebase and it will just work.
#ifndef defer
struct defer_dummy {};
template <class F> struct deferrer { F f; ~deferrer() { f(); } };
template <class F> deferrer<F> operator*(defer_dummy, F f) { return {f}; }
#define DEFER_(LINE) zz_defer##LINE
#define DEFER(LINE) DEFER_(LINE)
#define defer auto DEFER(__LINE__) = defer_dummy{} *[&]()
#endif // defer
Usage: defer { statements; };
Example:
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#ifndef defer
struct defer_dummy {};
template <class F> struct deferrer { F f; ~deferrer() { f(); } };
template <class F> deferrer<F> operator*(defer_dummy, F f) { return {f}; }
#define DEFER_(LINE) zz_defer##LINE
#define DEFER(LINE) DEFER_(LINE)
#define defer auto DEFER(__LINE__) = defer_dummy{} *[&]()
#endif // defer
bool read_entire_file(char *filename, std::uint8_t *&data_out,
std::size_t *size_out = nullptr) {
if (!filename)
return false;
auto file = std::fopen(filename, "rb");
if (!file)
return false;
defer { std::fclose(file); }; // don't need to write an RAII file wrapper.
if (std::fseek(file, 0, SEEK_END) != 0)
return false;
auto filesize = std::fpos_t{};
if (std::fgetpos(file, &filesize) != 0 || filesize < 0)
return false;
auto checked_filesize = static_cast<std::uintmax_t>(filesize);
if (checked_filesize > SIZE_MAX)
return false;
auto usable_filesize = static_cast<std::size_t>(checked_filesize);
// Even if allocation or read fails, this info is useful.
if (size_out)
*size_out = usable_filesize;
auto memory_block = new std::uint8_t[usable_filesize];
data_out = memory_block;
if (memory_block == nullptr)
return false;
std::rewind(file);
if (std::fread(memory_block, 1, usable_filesize, file) != usable_filesize)
return false; // Allocation succeeded, but read failed.
return true;
}
int main(int argc, char **argv) {
if (argc < 2)
return -1;
std::uint8_t *file_data = nullptr;
std::size_t file_size = 0;
auto read_success = read_entire_file(argv[1], file_data, &file_size);
defer { delete[] file_data; }; // don't need to write an RAII string wrapper.
if (read_success) {
for (std::size_t i = 0; i < file_size; i += 1)
std::printf("%c", static_cast<char>(file_data[i]));
return 0;
} else {
return -1;
}
}
P.S.: The local deferrer object starts with zz_
and not _
so it doesn't clutter the Locals window in your debugger, and also because user identifiers technically shouldn't start with underscores.
I presented a header-only implementation of Go-style defer
at CppCon 2014 (YouTube link); I called it Auto
. IMHO this is still far and away the best alternative out there in terms of teachability, efficiency, and absolute fool-proofness. In use, it looks like this:
#include "auto.h"
int main(int argc, char **argv)
{
Auto(std::cout << "Goodbye world" << std::endl); // defer a single statement...
int x[4], *p = x;
Auto(
if (p != x) { // ...or a whole block's worth of control flow
delete p;
}
);
if (argc > 4) { p = new int[argc]; }
}
The implementation looks like this:
#pragma once
template <class Lambda> class AtScopeExit {
Lambda& m_lambda;
public:
AtScopeExit(Lambda& action) : m_lambda(action) {}
~AtScopeExit() { m_lambda(); }
};
#define Auto_INTERNAL2(lname, aname, ...) \
auto lname = [&]() { __VA_ARGS__; }; \
AtScopeExit<decltype(lname)> aname(lname);
#define Auto_TOKENPASTE(x, y) Auto_ ## x ## y
#define Auto_INTERNAL1(ctr, ...) \
Auto_INTERNAL2(Auto_TOKENPASTE(func_, ctr), \
Auto_TOKENPASTE(instance_, ctr), __VA_ARGS__)
#define Auto(...) Auto_INTERNAL1(__COUNTER__, __VA_ARGS__)
Yes, that's the entire file: just 15 lines of code! It requires C++11 or newer, and requires your compiler to support __COUNTER__
(although you can use __LINE__
as a poor man's __COUNTER__
if you need portability to some compiler that doesn't support it). As for efficiency, I've never seen GCC or Clang generate anything other than perfect code for any use of Auto
at -O2
or higher — it's one of those fabled "zero-cost abstractions."
The original source also has a C89 version that works on GCC by exploiting some very GCC-specific attributes.
There is a proposal for std::unique_resource_t
which will enable code like
auto file=make_unique_resource(::fopen(filename.c_str(),"w"),&::fclose);
for resources, and it defines a general scope_exit
, which should be the same as defer
:
// Always say goodbye before returning,
auto goodbye = make_scope_exit([&out]() ->void
{
out << "Goodbye world..." << std::endl;
});
It will be considered for likely adoption in the Post-C++17 standard.