Adopting C++11: no-brainer features

C++11/14 is a significant change from C++98/03, and features like move semantics take a while to get used to. Also, people tend to be quite conservative about adopting new features (especially if they look unfamiliar). It took us in the games industry a while to move to C++ from C. But here are what I consider the no-brainer, programming-in-the-small things to adopt. Extra safety and expressiveness with zero runtime impact and pretty much no potential for misuse (never say never, but you’d really have to go out of your way).

1. using instead of typedef

Not only is it a whole two characters fewer to type, it’s easier to read in the common function pointer case, and you can use it for template aliases, which often saves a lot more verbosity in the rest of the code. No more typename everywhere! Compare:

// old and busted: typedef
template <class T>
struct Foo
  // I can't typedef Foo<T>::type 
  // and I have to use typename everywhere
  typedef T type;
// Typical usage: a function pointer
typedef void (*funcptr)(int);
// new hotness: using
template <class T>
struct Foo
  using type = T;
// This pattern is used a lot in C++14 STL
template <class T>
using foo_t = typename Foo<T>::type;
// Function pointer is easier to read
using funcptr = void (*)(int);

2. static_assert

Compile-time asserts; what’s not to like? Odds are you have a homegrown C++98 TMP version of this somewhere; now it’s part of the language. Extra safety for zero runtime cost.

3. nullptr

Again, extra safety for zero cost. Ditch your zeros and NULLs, and you can safely have functions overloaded on pointer and integral types.

4. scoped enums (enum class)

The compiler can help prevent accidental confusion between types, and the enum values don’t leak any more. Hurrah! (It’s like Haskell’s newtype for integers!)

5. forward declarations of enums, underlying type for enums

This works on (new) scoped and (old) unscoped enums alike. You don’t have to put in fake bit-width values to force the size of an enum any more, and you can hide the values separately from the declaration, so you don’t need to recompile everything when you add a value.

6. override

When (for example) the class you’re deriving from changes upstream, and now you’re accidentally not overriding a member function you thought you were… you’d really like to know that. With override, a bug that might take you a couple of hours to track down becomes a trivial-to-fix compile error. (Often mentioned in the same breath as override is final, and it’s fine, but much less usefully applicable.)

Now, there are also a couple of things to stop using.

1. rand()

Stop using rand(). Really, it’s bad. Deep down, we always knew it was “bad” but maybe we convinced ourselves it was OK for “small”/”quick-and-dirty” tasks. It isn’t. And now, it’s not any easier or faster than just doing the Right Thing with <random>. STL explains it all in rand() considered harmful.

2. bind

Lambdas supersede bind in every way. They’re more straightforward to write, easier to read, as powerful and at least as efficient if not more so, and just… you know, not so weird. And if you think lambdas take some getting used to, well, I don’t think I ever got used to bind

Leave a Reply