What Features I Like the Most in C++11 (Part 2)

My last post started a list of features in C++11 that I enjoy using and think C++ programmers should be aware of moving forward. However, the list was getting too long, so this post is a continuation of that list. In case you missed it, have a look at Part 1.

The list continues…

Defaulted and deleted class methods

Sometimes class types need to have certain constructors or operators either defaulted or deleted. In older versions of C++, this was only possible by defining empty methods and putting them in either public or private sections for the compiler to see whether or not they are enabled. While many of us had become accustomed to this pattern, C++11 gives us a way to say what we exactly intend. Consider the following example:

// MyClassType.h //
class MyClassType
{
public:
  MyClassType();
  MyClassType(int value);
  virtual ~MyClassType();

private:
  int   privateInt   {0  };
  float privateFloat {1.f};
  //...
};

// MyClassType.cpp //

MyClassType::MyClassType() {}
MyClassType(int value) : privateInt(value) {}
MyClassType::~MyClassType() {}

In this example, MyClassType has two private members with each having a defaulted value inlined in their declaration (letting us not need to repeat default initializers between the two constructors). The only reason that the default constructor is defined is to tell the compiler not to delete the one it would generate, as the introduction of the second constructor would tell the compiler to avoid generating a default constructor automatically. There are, however, some subtle things that can get thrown out if you use this pattern without thinking: constexpr and exception specification.

Basically, when the compiler does not generate something on your behalf it will follow exactly what you say. Thus if you do not mark your constructor as constexpr or nothrow, the compiler will respect that and pessimistically avoid certain optimizations. This is not good when there exists a visually equivalent (actually better, in my opinion) pattern with positive tradeoffs!

When the compiler does generate them on your behalf, it will generate versions that are maximally restrictive (based on what data members exist in the type) to enable as many compile-time optimizations as possible. To get these features, the class definition would be rewritten as:

// MyClassType.h //
class MyClassType
{
public:
  MyClassType() = default;
  MyClassType(int value);
  virtual ~MyClassType() = default;

private:
  int   privateInt   {0  };
  float privateFloat {1.f};
  //..
};

// MyClassType.cpp //

MyClassType::MyClassType(int value) : privateInt(value) {}

Another argument for this pattern is uniformity, as default/delete also applies to copy/move constructors and copy/move assignment operators. Using the new declaration syntax allows generated and deleted constructors to be all declared next to each other, without having the noise or confusion of making some of them public or private to get the same result. Thus if I wanted to make MyClassType a move-only type, I could very easily define it as:

class MyClassType
{
public:
  MyClassType() = default;
  MyClassType(int value);
  virtual ~MyClassType() = default;

  MyClassType(const MyClassType &) = delete;
  MyClassType(MyClassType &&) = default;
  MyClassType &operator=(const MyClassType &) = delete;
  MyClassType &operator=(MyClassType &&) = default;

private:
  //...
};

Now you can see that MyClassType can be constructed and moved, but not copied: all defined next to each other which helps make those characteristics quicker to realize when reading the class.

Compile-time assertion

I think most programmers are familiar with assert(): take some condition at run-time and trigger a fatal error if the condition is false. While this can be a crude way of detecting errors at run-time, C++11 brings the construct to compile-time making it a great way to detect errors by preventing ill-formed programs from compiling.

The keyword that for this is ‘static_assert()’, which takes a compile-time evaluated condition (or expression) and triggers a compile error if the condition is false. static_assert() also takes a compile-time string as an error message, giving programmers the ability to speak directly to the error and what the remedy should be. The importance of the error message should not be underestimated: nobody likes code which does not compile and have no hints as to how it can be fixed!

For example, in OSPRay we have a compile-time constant which governs the dimensions of a fame buffer tile that must be a positive power of 2. It is not important to know the details of why that is (or even what a tile is if you are not a rendering person), rather that there is an integer constant that has a specific requirement to be valid. In our CMake build system, we set it up so that users can only select values which are valid, but technically a user could hard define it to something else, where static_assert() comes to the rescue.

In general, you can place static_assert() in anywhere you like. Thus to test the above tile dimension validity, we define the following just above the Tile struct definition:

// assume TILE_SIZE is a preprocessor constant
static_assert(TILE_SIZE > 0 && (TILE_SIZE & (TILE_SIZE - 1)) == 0,
  "OSPRay config error: TILE_SIZE must be a positive power of two.");

Thus if TILE_SIZE is defined to something other than (2, 8, 16, 32, 64, …), then you get a compile error that tells you exactly what is wrong. Without it, OSPRay could compile, but it would be incorrect and unavoidably have crashes occurring everywhere tiles are used.

A huge part of doing static_assert() is utilizing type traits. These can be found in the standard library, Boost, or you can write them yourself. In general, type traits take in some type T and statically define whether some condition is true or false. This is very (!!) useful for ensuring that a template is used correctly. For instance, perhaps I want to define a template function which does some sort of arithmetic that preserves the passed in type, but I only want it to be instantiated with arithmetic types (int, float, double, etc). In pre-C++11 code, you could do explicit template instantiation with all the types which are valid, but this is a very verbose and repetitive solution to use for the goal of limiting instantiation to a category of types. Instead, we can write a single template with a static_assert() to get the same results:

#include <type_traits>
template <typename T>
T doSomeMath(T value)
{
  static_assert(std::is_arithmetic<T>::value,
                "doSomeMath<> can only be used with arithmetic types");

  // do some arithetic...
}

For a more “real world” example, I discussed in a previous post some custom type traits written to check the correct form of a callable object passed to a parallel_for() wrapper.

Smart pointers

Memory management in C++ is often a contentious topic among programmers as C++ is explicitly not a garbage collected language. Instead, C++ uses its strength (RAII) as a way to track resources, including dynamically allocated memory. The C++11 standard library brings multiple tools to address this problem, each with different semantics. I want to focus on 3 of them: std::unique_ptr, std::shared_ptr, and std::reference_wrapper.

Move semantics have changed the way that C++ is written as it provides some much needed optimizations when utilizing simple value-semantics in code. It enables us to implement pointers which are cheap to move around, taking much of the “I use plain pointers because they are faster” argument out of the picture. Thus if you are skeptical of their performance, I would encourage you to reconsider that position in light of C++11 and beyond.

There are two categories of smart pointers: pointers which are single owners (std::unique_ptr) and pointers which are shared owners (std::shared_ptr).

std::unique_ptr

When a pointer needs to share ownership with other instances, there must be some sort of reference counting implemented to ensure proper lifetime tracking: this incurs overhead when doing copy/move operations between pointers. However, std::unique_ptr guarantees single ownership by being a move-only type, where two instances of std::unique_ptr cannot point to the same object (well, without going out of your way to break it). Thus because we can guarantee that only one std::unique_ptr owns an object, we can throw away reference counting (and its associated overhead). Consider the following code:

std::unique_ptr<int> p1 (new int);
std::unique_ptr<int> p2 (new int);
p2 = p1;

In this example, p2 would own the dynamically allocated integer, and p1 would be NULL. During the assignment of p1 to p2, 1) p2 would delete the memory it previously had, 2) receive ownership of p1’s memory, and 3) p1 would no longer point to its memory (i.e. it becomes NULL). This has tremendous advantage over plain pointers as the lifetimes of each object are both automatically handled and it is well specified when memory operations (such as freeing memory) will occur, should that be important to you.

I will also point out that the above code is technically not exception safe, where constructing a std::unique_ptr with explicitly calling ‘new’ using non-trivial types may throw an exception on construction. The preferred fix is to use a helper function which does proper construction and will properly clean itself up if an exception is thrown. Unfortunately C++11 missed this for std::unique_ptr, but did include it for std::shared_ptr (std::make_shared<>). C++14 adds it by including a std::make_unique<> function, but if you are stuck with only C++11 you can easily implement it yourself. You can copy the following implementation of make_unique<> for your own code:

template <typename T, typename ...Args>;
inline std::unique_ptr<T> make_unique(Args ...args)
{
  return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
}

std::shared_ptr

In some cases it makes sense to have more than one object track the lifetime of some dynamically allocated object. In this case, std::shared_ptr is the right tool for the job. When an assignment of one std::shared_ptr is made to another, both end up pointing to the same dynamically allocated object. Then as each std::shared_ptr instance goes out of scope the allocated object will remain in memory until the last one is destroyed.

Some have called std::shared_ptr “just as good as a global variable”, where it is preferred to use std::unique_ptr until it is necessary to have shared state. I personally think std::shared_ptr is not quite as bad as a global variable, but I like to heed the advice of avoiding it until the problem mandates it is necessary.

It is important to know that std::shared_ptr reference counting is atomic, meaning that std::shared_ptr instances can be on different threads and still correctly track the lifetime of the underlying object. This is important for two reasons. First, you can safely do common operations with it between multiple threads. There are limits to this as the entire std::shared_ptr interface is not guaranteed to be thread safe, but at least the reference counting will not race. Second, programmers with performance-sensitive code should know the cost of doing atomic read/writes when using std::shared_ptr. If you have a highly contended object lifetime you should reconsider doing lots of copies or moves of std::shared_ptr in that section of your code anyway, but know that atomics have a real (even if small) cost.

One aspect of using std::shared_ptr is to beware of dependency cycles. For instance, if I have some sort of graph data structure that defines some ‘Node’ data type, beware that you cannot have a Node contain a std::shared_ptr to another node and the other Node contain a std::shared_ptr back to the original node. In order to break cycles, C++11 added std::weak_ptr, which allows for temporary access to another std::shared_ptr’s object. There are other options for breaking dependency cycles as well, such as using a plain pointer or std::reference_wrapper, but I do not have a clear favorite solution as I have not had a reason to write data structures with such dependency issues.

Finally, just as it is recommended to use make_unique<> to create std::unique_ptr instances for exception safety, it is equally important to use std::make_shared<> for std::shared_ptr!

std::reference_wrapper

While it is not really a “smart pointer”, I also wanted to point out C++11’s std::reference_wrapper as an intriguing type. Basically it is a way to hold onto a reference of another object which can be reseated later. I think of it as a plain pointer which, by definition, can never be NULL because you cannot construct an invalid std::reference_wrapper. I have not used it extensively yet, but I definitely like the idea of having a reseatable reference as a more expressive replacement for some usages of plain pointers.

Threading

Instead of going through each threading related feature in depth, I want to make aware what threading related features came into existence in C++11. Pre-C++11 code which was multithreaded required either 3rd party libraries to have platform independent threading primitives (e.g. TBB, Qt, or Boost) or the use of non-portable, platform specific threading APIs (e.g. pthreads). With C++11, the following are all now cross platform primitives found in the standard library:

With additional useful tools:

Finally, C++11 got a start on parallel programming constructs with things like std::async and std::future. These constructs have plenty of issues (such as std::future not having continuations) but they are definitely a start!

I did not include a conclusive list of multithreading features available since C++11, but you can find that here.

Final thoughts

That’s it for this week. Not much to say beyond these two posts. C++11 adds so many features and library components that it certainly feels like a new language, and I feel like skeptical users ought to view it as one (for better, not for worse!).

Until next time, happy coding!

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s