Metric Panda Games

One pixel at a time.

The C++ Subset

Rival Fortress Update #20

I like to keep an eye on new developments of the C++ spec and the direction the language is moving, but in the code for Rival Fortress I don’t use most of the features that C++ has to offer.

The features I don’t like

I don’t use object-oriented programming. Most of my code is written in a functional programming style, as I’m a big fan of data-oriented design.

I rarely use inheritance, and if I use it, I stay away from virtual functions or deep hierarchies. I find the code to be cleaner and easier to understand when one doesn’t need to study deep class hierarchies to figure out what a piece of code is doing.

I’m also not a big fan of the Standard Library as I find that most of the functionality it provides is either less performant than ad-hoc implementations or tends to hide complexity that I’d rather have out in the open.

I rarely use templates as most of the problems they address can be solved with code generation as I talked about in my reflection preprocessor post. Templates also bloat compile times considerably and produce god-awful compiler error messages.

I never use exceptions as I find them to be a useless performance penalty that can be easily avoided with careful coding. They also increase code complexity because of all the redundant try/catch blocks that tend to pollute codebases that use them.

I don’t use RTTI as a lot of the code that would require introspection is generated by Rival Fortress’s engine preprocessor.

I don’t use RAII and very rarely use object constructor/destructor pairs. I find that RAII is unnecessary when coding in a functional style.

The features I like

Most of my code is C99-like, with the exception of the following features that I found useful:

  • Operator overloading: when it makes sense, like for simple vector math (i.e. addition, subtraction, scalar multiplication).
  • Function overloading: I try to use self-documenting function names, but I find that in C the lack of function overloading can force you to either use verbose or cryptic names when the same logical function needs to operate on different data types.
  • const_expr: is sometimes useful when I don’t want to go the macro route and need something to be evaluated at compile time. For example, string length and string hashing have both a runtime and a const_expr version.
  • static_assert: I find it very useful especially for sanity checks and paired with const_expr functions or for validation of meta programmed code generated by the engine’s preprocessor.
  • raw string literals: are very useful when dealing with long string. I use them very often while working on the engine preprocessor.
  • lambdas: are sometimes useful as a convenience feature, but most of the times I just use function pointers.