After a week off from writing I have decided to add another post in my C++11 favorite features series. I took a look back at Part 1 and Part 2 and (with some prodding from Twitter) noticed there are a few other features I love using in C++11. The list is getting pretty long now, but I truly find great use for these in the right places at the right time.
Checking methods ‘override’ at compile-time
Class hierarchies are common in many C++ codebases, where runtime polymorphism can be leveraged through virtual functions. Before C++11, knowing if a class method correctly overrides a base class method was a bit dicey because a method only overrides a base class method if it exactly matches its signature. Consider the following simple example:
class Base { public: virtual std::string toString() const { return "Base"; } }; class Derived { public: std::string toString() { return "Derived"; } }; int main() { Base *b = new Derived; std::cout << b->toString() << std::endl; delete b; }
In this case, Derived::toString() never gets called because it does not override Base::toString() due to the (lack of) const qualifier on the method. This can get very sneaky in the wild as methods that take multiple parameters have even more places that the method signature may not line up, even though you think it does!
C++11 gives us a tool to directly express our intent, allowing the compiler to catch the issue as an error. By simply adding the ‘override’ keyword to the function declaration (which happens to also be the definition in the above example) we would get a compile error. Thus adding both the missing ‘const’ and ‘override’, the example would be written as:
class Base { public: virtual std::string toString() const { return "Base"; } }; class Derived { public: virtual std::string toString() const override { return "Derived"; } }; //...
This would then correctly print out the expected string when holding a Base pointer to Derived. While I tend to use contrived examples to boil down features to their essence, features like this are incredibly valuable in the wild. I recently did some refactoring in OSPRay to some of our sample geometry viewer applications and a really good use case came up: if you change the name of a method in a base class and miss changing the name in a derived class, without ‘override’ you will inevitably compile (and run) broken code. Thus it not only saves you when you initially write a class hierarchy, it also guards you from introducing bugs when making changes at a later time.
Lastly, the override keyword has absolutely no downsides as there is no performance (compile-time or run-time) penalty for using the new keyword, thus I think you should always use it where it applies. You can even have the compiler give you a warning to suggest where it should be added (gcc/clang “-Wsuggest-override” flag), or have ‘clang-tidy’ simply fix your code for you!
Range-based ‘for-loop’
This one I like for simplicity and readability. C++11 brings a new for-loop syntax for the purpose of iterating over an entire container (rather, anything that satisfies std::begin and std::end). If I want to iterate over an entire std::vector, it is common to write a “C-like” for-loop because std::vector can be accessed with the same interface as a plain array:
for (int i = 0; i < myVector.size(); ++i) { doSomething(myVector[i]); }
However if we want to generalize to containers beyond std::vector, we start using iterators:
/* template <typename Container> */ // ... for (Container::iterator itr = myContainer.begin(); iter != myContainer.end(); ++i) { doSomething(*itr); }
…which gets a little bit nicer with ‘auto’ deducing the iterator type:
for (auto itr = myContainer.begin(); iter != myContainer.end(); ++i) { doSomething(*itr); }
However, range-based for-loops allow us to simplify even further by writing it as:
for (auto value : myContainer) { doSomething(value); }
Simply put: if you are doing something very simple, it is beter to use a minimalistic way of writing it as each version of the above loop do the same thing. I also like Scott Myers’s advice for communicating if a loop does any mutating operations on the data in the container by using the constness of the element variable to indicate this. Thus I recommend using these to indicate a loop which simply read data:
// 'value' is a mutable copy of each element for (auto value : myContainer) {} // 'value' is a const copy of each element for (const auto value : myContainer) {} // 'value' is a const reference to each element for (const auto& value : myContainer) {}
…and using the following to indicate a loop which transforms each element:
// 'value' is a mutable reference to each element for (auto &value : myContainer) {}
Random numbers from the standard library
The standard library received a nice upgrade in C++11 with support for both random number generation and random number distributions. Prior to C++11, the only built-in tool available was rand() inherited from C, which does not provide the ability to configure the generation algorithm nor the ability to map it to a random number distribution. Boost had a nice random number library in the early 2000s, but that required an external dependency that was not yet standardized.
As stated before, the C++ standard random header comes with two parts: a set of predefined random number generators and a set of random number distributions. I am no statistics expert, so I think it is nice to mess around with different distributions and not be concerned with understanding the details of implementing them correctly (trust your standard library implementers!). As a graphics programmer I most commonly encounter the need for normal float distributions from 0.0 to 1.0. Writing this using C++11 is trivial:
#include <random> #include <iostream> int main() { std::default_random_engine engine; std::uniform_real_distrigution<float> dist(0.f, 1.f); for (int i = 0; i < 10; ++i) std::cout << dist(engine) << std::endl; return 0; }
Take a look at some documentation on C++ standard random numbers to see all the possibilities. Note that there is a good way to seed a generator by using std::random_device. However, be warned that it is potentially very expensive to use std::random_device, so I recommended using it only as a way to first seed a generator.
Finally, I will to point out that both the random number generator and random number distribution interfaces are extendable. Even though there are many generators and distributions available, the standard library makes it straightforward to implement your own and use it with other standard library components.
Thread-local static storage
The last feature I want to point out is the new ‘thread_local‘ keyword, which is a storage modifier keyword for variables which have static storage duration. If you have some sort of static variable, you may want every thread to have its own independent private copy instead of a single instance being shared. This is particularly useful for performance and avoiding unnecessary data races: shared state between threads is a great way to generate bugs and performance problems.
My favorite use of thread_local is for random number generators used for shading functions inside of a ray tracer. For example, OSPRay uses a thread pool to manage rendering tasks where each task works on a subset of the frame buffer. If ambient occlusion is being calculated for each pixel, then a uniform distribution is used to sample a hemisphere of secondary rays to determine lighting on a surface. I can easily give each thread its own private copy of a static random engine and use a shared distribution to get random numbers from each engine. Here is an example:
/* shade_ao.h */ extern thread_local std::default_random_engine ao_rng; inline void shade_ao(/*...*/) { //... static std::uniform_real_distribution<float> dist(0.f, 1.f); auto value = dist(ao_rng); //... } /* shade_ao.cpp */ thread_local std::default_random_engine ao_rng;
This example shows an inlined function shade_ao(), where every thread executing a task which calls shade_ao() will have a different random number generator. It does not matter that each task shares the std::uniform_real_distribution as that does not mutate once it is instantiated. However, if you use the same pattern for other types of objects you can just as easily throw in ‘thread_local’ in function scoped static variables, too.
Final thoughts
I hope you continue to use C++11’s new language features and library tools to write better code for both humans to understand and the machine to run. My future posts will assume that you are now at least familiar with C++11 now that you should have some insight into why certain new features exist and how they can be used effectively.
Until next time, happy coding!