ftz.Lyberta.net

Modern C++ goodness

C++ design

When designing code for ftz project, C++ Core Guidelines are assumed by default. After that, a few rules override them and a few rules complement them.

Contents

Overrides

Sink parameters: pass by value and move inside the function (overrides F.18)

The fastest implementation is two overloads: One taking const T& and another taking T&&. However, the only gain of such implementation compared to the single function taking T is a single move constructor call. As I have never seen expensive move constructors in practice, I’ve chosen the design that is easier to implement and maintain.

class Titanic
{
	Iceberg whynot;
public:
	void Sink(Iceberg iceberg)
	{
		whynot = std::move(iceberg);
	}
};

Prefer placing each class in a separate file (overrides NR.4)

In my experience, it is much easier to navigate through the code this way, especially if you are dealing with the code that was written by somebody else. Sure, there is grep but a lot of people don’t know how to use it and IDEs are not very helpful when you use features that are available in compiler but not IDE.

Make all your classes and structs final by default (overrides C.139)

If your class is not designed to be derived from, be explicit and disable it. This is a good documentation.

Complements

Don’t include C-style headers

They pollute the global namespace.

// Bad
#include <stdio.h>

// Good
#include <cstdio>

Don’t use using namespace std;

It imports a lot and, as a result, makes code more ambiguous and may introduce subtle bugs.

When starting .cpp file, always include corresponding .h file first.

This avoids hidden dependencies and insures your header is self sufficient.

Do not use variable-width types like short or int. Create an alias with a meaningful name and use fixed-width type as an underlying type

This makes it easy to transmit the data via network or filesystem.

// Old C++
unsigned short index = 0;

// Modern C++
using MyIndex = std::uint16_t;
MyIndex index = 0;

Throw standard exception classes if possible

When throwing an exception, specify the function the exception is thrown from.

void DoSomething(int a)
{
	if (a < 0)
	{
		throw std::invalid_argument{"DoSomething: a is less than zero."};
	}
	/* ... */
}

Use non-member std::begin and friends instead of member ones

They are more flexible because not all classes have these member functions. Non-members also work with raw arrays.

// Old C++
for (std::vector<std::string>::iterator i = myvector.begin(); i != myvector.end(); ++i)
{
	/* ... */
}

// Modern C++
for (auto i = std::begin(myvector); i != std::end(myvector); ++i)
{
	/* ... */
}

Use lambdas instead of std::bind

std::bind does type erasure which has runtime costs and will always be slower than lambdas without any gain in flexibility.

Use std::function instead of function pointers

std::function is much more advanced and allows to store arbitrary state.