gingerBill

  • Home
  • Articles
  • Podcast
  • Odin
  • Subscribe

A Defer Statement For C++11

2015-08-19

One of my favourite things about Go is the defer statement. The defer statement pushes a function call onto a list; the list of saved calls in called when the function returns.

Imitating this is C++ is impossible. Instead of calling when the function calls, you can call at the end of scope; this is a better approach for C++. This is similar to how D has scope(exit).

C++11 Implementation

template <typename F>
struct privDefer {
	F f;
	privDefer(F f) : f(f) {}
	~privDefer() { f(); }
};

template <typename F>
privDefer<F> defer_func(F f) {
	return privDefer<F>(f);
}

#define DEFER_1(x, y) x##y
#define DEFER_2(x, y) DEFER_1(x, y)
#define DEFER_3(x)    DEFER_2(x, __COUNTER__)
#define defer(code)   auto DEFER_3(_defer_) = defer_func([&](){code;})

Explanation

One of the most common examples for this in Go is files.

import "os"

func someFunc() {
	file, err := os.Open("filename.ext", os.O_RDONLY, 0)
	if err != nil {
		// handle error
		return
	}
	defer os.Close(file)
	// Do whatever

	return // No need to close file explicitly
}

In C/C++, before every return, fclose must be called.

void some_func(void) {
	FILE *file = fopen("filename.ext", "rb");
	if (file == NULL) {
		// handle error
		return;
	}

	if (someTest) {
		// ...
		fclose(file); // Have to explicitly close file
		return;
	}


	fclose(file); // Have to explicit close file
}

You may say that RAII in C++ solves this issue but this would require a class wrapper. defer allows for the same thing as RAII but without manually creating a wrapper type. Also, defer conceptually links the creation with the destruction without hiding it.

Examples In C++ Code

void some_func(void) {
	FILE *file = fopen("filename.ext", "rb");
	if (file == NULL)
		return;
	defer (fclose(file));

	u8 *buffer = (u8 *)allocate(64 * sizeof(u8));
	if (buffer == NULL)
		return; // fclose is called automatically
	defer (deallocate(buffer));

	defer ({
		printf("You can even defer ");
		printf("an entire block too!");
	});

	// Do whatever...
}

I use this regularly within C++ code as it is very useful when dealing with C code bases.

There are numerous other implementations of this too and I have linked to these below.

Other Implementations

  • Ignacio Castaño
  • Kristoffer Grönlund (Page is missing)

Caveat

Because the defer statement is called at the end of scope rather than function return, a Go programmer for example would do something like this:

void another_func(void) {
	for (int i = 0; i < 4; i++)
		defer (printf(" %d", i));
	printf(" Hello");
}

to return Hello 3 2 1 0. However, it will return 0 1 2 3 Hello. I personally prefer the scope behaviour but that is just a matter of opinion.

© 2007–2024 Ginger Bill