Neat defer macro for C++

Manual resource management in low level C-style C++ code might be annoying. It's not practical to create good enough RAII wrappers for every single C API you use, but approaches with goto cleanup or loads of nested if (success) hurt readability.

A Go-inspired defer macro to the rescue! The usage is as simple as that:

void* p = malloc(0x1000);
defer [&] { free(p); };

The deferred lambda will be executed on scope exit, no matter how it happens: you can return from any point, throw an exception (if allowed), or even use a goto to an outer scope.

Macro implementation is concise and relies on basic features of C++17 (Clang 5+, GCC 7+, MSVC 2017+):

#ifndef defer

template <typename T>
struct Deferrer
{
	T f;
	Deferrer(T f) : f(f) { };
	Deferrer(const Deferrer&) = delete;
	~Deferrer() { f(); }
};

#define TOKEN_CONCAT_NX(a, b) a ## b
#define TOKEN_CONCAT(a, b) TOKEN_CONCAT_NX(a, b)
#define defer Deferrer TOKEN_CONCAT(__deferred, __COUNTER__) =

#endif

It is truly zero-cost and doesn't rely on C runtime or standard library, so it can be used even in kernel development.

Let's compare!

Naive version

Let's imagine a function where all successfully acquired resources are explicitly released on every error:

bool MakeDumpToFile(DWORD pid, PCWCHAR filename)
{
	HMODULE dbgdll = LoadLibraryA("dbghelp.dll");
	if (!dbgdll)
	{
		return false;
	}

	auto pfnMiniDumpWriteDump = (decltype(&MiniDumpWriteDump)) GetProcAddress(dbgdll, "MiniDumpWriteDump");
	if (!pfnMiniDumpWriteDump)
	{
		FreeLibrary(dbgdll);
		return false;
	}

	HANDLE proc = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);
	if (!proc)
	{
		FreeLibrary(dbgdll);
		return false;
	}

	HANDLE file = CreateFileW(filename, GENERIC_WRITE, NULL, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
	if (!file || file == INVALID_HANDLE_VALUE)
	{
		CloseHandle(proc);
		FreeLibrary(dbgdll);
		return false;
	}

	bool result = pfnMiniDumpWriteDump(proc, pid, file, MiniDumpNormal, NULL, NULL, NULL);

	CloseHandle(file);
	CloseHandle(proc);
	FreeLibrary(dbgdll);

	return result;
}

Eww, disgusting! So many duplicated lines of code, so easy to make a mistake and forget to free something!

Classic goto cleanup

The same function, but in the classic goto cleanup style:

bool MakeDumpToFile(DWORD pid, PCWCHAR filename)
{
	bool result = false;
	HMODULE dbgdll = NULL;
	decltype(&MiniDumpWriteDump) pfnMiniDumpWriteDump = nullptr;
	HANDLE proc = NULL;
	HANDLE file = NULL;

	dbgdll = LoadLibraryA("dbghelp.dll");
	if (!dbgdll) { goto cleanup; }

	pfnMiniDumpWriteDump = (decltype(&MiniDumpWriteDump)) GetProcAddress(dbgdll, "MiniDumpWriteDump");
	if (!pfnMiniDumpWriteDump) { goto cleanup; }

	proc = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);
	if (!proc) { goto cleanup; }

	file = CreateFileW(filename, GENERIC_WRITE, NULL, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
	if (!file || file == INVALID_HANDLE_VALUE) { goto cleanup; }

	result = pfnMiniDumpWriteDump(proc, pid, file, MiniDumpNormal, NULL, NULL, NULL);

cleanup:

	if (file && file != INVALID_HANDLE_VALUE)
	{
		CloseHandle(file);
	}

	if (proc)
	{
		CloseHandle(proc);
	}

	if (dbgdll)
	{
		FreeLibrary(dbgdll);
	}

	return result;
}

You can't goto through variable declarations so it requires to declare all variables in advance. It's also a bit less effective because the cleanup part should check if every resource is valid and requires to be released, and you can accidentally forget to free something or do it in wrong order because it's far from the code that acquires the resources so it's harder to notice an error.

Nested if success

With the nested if (success) approach our function would become:

bool MakeDumpToFile(DWORD pid, PCWCHAR filename)
{
	HMODULE dbgdll = LoadLibraryA("dbghelp.dll");
	if (dbgdll)
	{
		auto pfnMiniDumpWriteDump = (decltype(&MiniDumpWriteDump)) GetProcAddress(dbgdll, "MiniDumpWriteDump");
		if (pfnMiniDumpWriteDump)
		{
			HANDLE proc = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);
			if (proc)
			{
				HANDLE file = CreateFileW(filename, GENERIC_WRITE, NULL, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
				if (file && file != INVALID_HANDLE_VALUE)
				{
					bool result = pfnMiniDumpWriteDump(proc, pid, file, MiniDumpNormal, NULL, NULL, NULL);
					CloseHandle(file);
					return result;
				}
				CloseHandle(proc);
			}
		}
		FreeLibrary(dbgdll);
	}
	return false;
}

An improvement, but you'd better have a really wide monitor for this!

WTF std::unique_ptr

The same, but with taste of STL:

bool MakeDumpToFile(DWORD pid, PCWCHAR filename)
{
	std::unique_ptr<std::remove_pointer_t<HMODULE>, decltype(&FreeLibrary)> dbgdll(LoadLibraryA("dbghelp.dll"), &FreeLibrary);
	if (!dbgdll) { return false; }

	auto pfnMiniDumpWriteDump = (decltype(&MiniDumpWriteDump)) GetProcAddress(dbgdll.get(), "MiniDumpWriteDump");
	if (!pfnMiniDumpWriteDump) { return false; }

	std::unique_ptr<std::remove_pointer_t<HANDLE>, decltype(&CloseHandle)> proc(OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid), &CloseHandle);
	if (!proc) { return false; }

	std::unique_ptr<std::remove_pointer_t<HANDLE>, decltype(&CloseHandle)> file([&]{
		auto h = CreateFileW(filename, GENERIC_WRITE, NULL, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
		return (h != INVALID_HANDLE_VALUE) ? h : NULL;
	}(), &CloseHandle);
	if (!file) { return false; }

	return pfnMiniDumpWriteDump(proc.get(), pid, file.get(), MiniDumpNormal, NULL, NULL, NULL);
}

STL, as usual, delivers the best WTF experience. This hacky approach is provided here for completeness' sake. Some people really use std::unique_ptr with custom deleters to manage non-pointer resources, even though template argument deduction does not help here, requiring you to specify all those verbose types every time. It has an important limitation: the resource must appear as nullptr in an invalid state, which is not always the case, and you have to deal with this somehow using additional hacks and tricks.

And finally, defer!

We can rewrite it with our defer macro this way:

bool MakeDumpToFile(DWORD pid, PCWCHAR filename)
{
	HMODULE dbgdll = LoadLibraryA("dbghelp.dll");
	if (!dbgdll) { return false; }
	defer [&] { FreeLibrary(dbgdll); };

	auto pfnMiniDumpWriteDump = (decltype(&MiniDumpWriteDump)) GetProcAddress(dbgdll, "MiniDumpWriteDump");
	if (!pfnMiniDumpWriteDump) { return false; }

	HANDLE proc = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);
	if (!proc) { return false; }
	defer [&] { CloseHandle(proc); };

	HANDLE file = CreateFileW(filename, GENERIC_WRITE, NULL, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
	if (!file || file == INVALID_HANDLE_VALUE) { return false; }
	defer [&] { CloseHandle(file); };

	return pfnMiniDumpWriteDump(proc, pid, file, MiniDumpNormal, NULL, NULL, NULL);
}

This looks much better! No excessive nesting, no so much hated goto, no duplicated lines of code.

Why this syntax?

Well, what other syntax could it be? Let's see...

defer free(p);

Go-like syntax. Unfortunately, it can't be implemented as a C++ macro.

defer(free(p));

No way. It looks like free(m) is called immediately, and it's result is passed to defer. Also, it does not allow to defer a few lines of code that is useful sometimes.

defer { free(p); };

It's better, but we can't control if we want to use a reference or a copy of outer variables that is important in some cases.

defer [&] { free(p); };

Our syntax. It expects a proper lambda, providing flexibility to control whether it captures variables by reference or by copy. In fact, it allows to defer any callable, not just a lambda, so even the semicolon after the closing brace looks reasonable.

There is also a proposal to add defer to C, and it uses exactly this syntax.

C++26

The macro implementation is even simpler with the upcoming C++26, which includes unnamed variable support (Clang 18+, GCC 14+):

#ifndef defer

template <typename T>
struct Deferrer
{
	T f;
	Deferrer(T f) : f(f) { };
	Deferrer(const Deferrer&) = delete;
	~Deferrer() { f(); }
};

#define defer Deferrer _ =

#endif

Comments are temporarily closed.