C++11 was the largest change ever made to C++; and due to the changed release schedule, probably will remain the largest single change. It is a well thought out, mostly backward-compatible change that can cause you to completely rethink the way you write code in C++. It is best thought of as almost a new language, a sort of (C++)++ language. There are too many changes to list here, and there are excellent resources available, so this is meant to just give you a taste of some of the most useful changes.
Many of the features work best together, or are related. There already are great resources for learning about C++11 (listed at the bottom of this lesson), and C++11 is already in use in most software. Therefore, the remainder of this lesson will cover a few of the common idioms in C++11 that a programmer experienced with the older C++ might not immediately think of.
Types
Typing in C++11 is much simpler. If the type is deducible, auto
allows you to
avoid writing it out. A few examples of uses of auto:
// Avoiding double writing on pointers
auto pointer = new SomeNamespace::MyLongType(arg1, arg2);
// Does anyone know the type of an iterator? It's ugly!
auto iterator = some_vector.begin();
// This is useful for prototyping, but probably should be explicitly typed in real code to help coders
auto type = function_returns_something();
// Worst use of auto; don't do this
auto a = 1;
When this first came out, the recommendation was to only use auto in cases where
it improves readability and understanding, and to about using it where it could
obscure the type. Later iterations of the language have continued to add uses
for auto
, including on some places where it is not interchangeable with some
physical type.
The NULL
keyword, which is equivalent to 0, causes typing issues. A new
nullptr
keyword was added, and is not equivalent to zero. This is
significantly better type-safety, and should always be used instead of NULL
.
The definitions of const
and mutable
changed a little;
see this video.
In short: const
actually means bit-wise constant or thread-safe; mutable means
the object is already thread-safe (atomics, mutexes, some queues). This is
probably what you thought they meant (but didn’t) in C++98. Thread safety is
important, so use const
where you can!
You can define a new type alias with using NewType = OldType
, which replaces
the confusingly ordered typedef OldType NewType
. This is a slightly more
powerful syntax, as well, since it allows you to provide an alias that partially
specializes a template.
Containers and iterators
While iterators existed in previous versions, using them is now part of the language, with the iterating for statement (for each). It allows a unintuitive iteration loop to be written more cleanly and compactly. Compare the following two methods of setting the values of a vector to zero:
for(vector<double>::iterator iter = vector.begin(); iter != vector.end(); ++iter)
*value = 0.0;
for(auto &&value : vector)
value = 0.0;
To make something iterable, you should define a begin
and end
method or
function. There are several options, as well. Using const &&
avoids making a
copy but still ensures that you don’t change the original iterable.
A related improvement is the addition of container constructors. Which means you can finally do this:
std::vector<int> values = {1,2,3,4,5,6};
C++ also has a variety of different initializers; C++11 added a uniform
initializer syntax Object{}
, so it has even more different initializers. The
benefit is that it works in cases that the old syntax had issues. It solves the
Most Vexing Parse, where a new object cannot be followed by parenthesis, since
that is identical to a function definition. It also does not explicitly narrow.
Due to the addition of initialiser lists, though, this can be confusing. What does the following statement produce, a list of 42 integers, or one integer with the value 42?
std::vector<int> vector{42};
Answer (click to expand)
The standard oddly prioritizes
the List Initializer, if the object in question supports List Initializer, and
so this is one vector of the value 42. The only way to initialize 42 empty
values is to use the old syntax, std::vector<int> vector(42);
, making this
Uniform Initializer Syntax definitely not Universal Initializer Syntax.
This might show up most often in member initializers, which were added to C++11 also; these have the same caveat.
Lambda functions
Functions are now easier to refer to and create. A std::function
type is
useful, but usually will be hidden with auto
unless you are crafting a
function to take functions as arguments. The lambda function allows an inline
function definition, with some perks. The syntax is [](){}
, which looks like a
normal function definition with the function name and type replaced by the
square brackets. For example:
auto square = [](double x){return x*x;};
double squared_five = square(5.0);
The lambda function gets interesting when you add something to the square brackets; this is called “capture” and allows you to capture the surrounding variables. For example:
int i = 0;
auto counter = [&i](){return i++;};
counter(); // returns 0
counter(); // returns 1
You can capture by value, by reference, etc. If you use [=]
or [&]
, the
lambda function will automatically capture (by value or reference, respectively)
any variables mentioned inside the function. Note that if you do use capture,
you will lose the ability to convert to a old-style C function pointer.
These are incredibly powerful, and can completely change how you program. You
should write functions to take callable like std::function
, and avoid C-style
function pointers; Lambda functions do satisfy C-style function pointers, but
only if they are captureless.
Class improvements
Default values for members can be declared in the definition now (as I’m sure
you’ve tried to do in older C++ at least once). You can also call a previous
constructor in the initializer list (delegating constructors). You can use
= default
and = delete
to specify default or deleted constructors of various
sorts. And, of course, move semantics provide move constructors and assignment.
class MyClass {
x = 0;
MyClass() = default;
MyClass(int y) : MyClass() {...}
};
A related improvement was made to enums; you can now make a strongly typed enum
with enum class
. These do not leak scope, like a normal enum value, and can
have a specified base type. They also do not implicitly convert.
Compile time improvements
The slow removal of the ugly, error-prone macro programming has started in
C++11, with constexpr
. A function or class with this modifier (and lots of
restrictions as to what it can contain) can be used by the compiler at compile
time to produce a result in your compiled code. It was very limited in C++11,
but has received lots of updates in each version since.
Variadic templates
Variadic templates allow a function or constructor to take an unlimited number
of arguments of any type. This allows the std::tuple
feature and other very
powerful features. Although the implementation may seem unusual at first,
especially if you are used to a scripting language like Python, the
implementation allows the parameter expansion to happen at compile time, meaning
there is no runtime penalty for using variadic templates.
The syntax uses the following constructs:
- Declaring a parameter pack. This is denoted by an ellipses to the left
of a parameter name. The ellipses are usually placed next to the proceeding
type, such as in
template<typename... Ts>
orTs... values
, but the meaning is the same. For a class this must be the last parameter; for a function it can occur earlier (but usually does not). - Expanding a parameter pack. This is denoted by an ellipses to the right
of a parameter pack or expression containing a parameter pack. This is often
seen in the body of the functions. These are commonly used to call functions,
funct(values...)
, or perform an expression then call a functionfunct(do_something(values)...)
. In the second example,do_something
is called on each value before calling thefunct
function. - Counting the parameters in the parameter pack. This is done with the
sizeof...()
function, which is aconstexpr
function that returns the number of parameters inside the parameter pack.
Variadic template function example (click to expand)
An example of a Python style print function:
void print() {
std::cout << std::endl;
}
template<typename T, typename... Ts>
void print(T value, Ts... values) {
std::cout << value << " ";
print(values...);
}
Here, the ellipses serves the same role the first two times it is seen; it is
declaring a parameter pack values
with type Ts
. Inside the function, the
ellipses is expanding the call to print(first value, second value, etc)
. This
recursively calls itself, ending in the final empty argument form; a very common
pattern for variadic templates.
Note
There is an old feature called variadic functions from C, which allows an unlimited number of arguments, but is not type safe. This is how the unlimited argument
printf
function is defined. This uses an ellipsis at the end of the parameter list, optionally after a comma.
For more on variadic templates and what they can do, see Cpp Reference’s page on parameter packs.
Move semantics
One of the more fundamental changes in the language was the promotion of move semantics to a language feature, as well as stronger guidelines on auto-optimization. This can fundamentally change the way functions are written. What is the problem with this statement in C++03?
GiantObject item = GiantObject_returning_function();
Here, you create a GiantObject
inside the function, and then copy it to a new
object item
, then delete the old object. It’s horribly wasteful in both time
and memory; if you don’t have enough memory for two separate copies of
GiantObject
, you can crash your program. There are two solutions in C++11; one
is called “move semantics”, and the other is “copy elision”. In C++11, the
compiler is generally recommended to do copy elision if possible, but it is
never required to do so by the language; in C++17, this become an official
requirement of the language in simple cases. Note that you should not use move
semantics to take advantage of this feature (don’t worry, though; newer
compilers will warn you if you use move semantics on something it could
copy-elide).
In copy elision, the outer scope simply takes ownership of the object already created. In move semantics, the stack-based portion of the object is copied, but the heap based portion is transferred without copy (on well written objects). For example, if you move a std::vector, you will keep the same memory and will only copy the stack parts (length and pointers). For a std::string, it is a little more complicated due to short-string optimisation (SSO), which puts small strings in a preallocated portion of the object; if you have a short string, moves and copies are the same, but for a long string, moves will be more efficient.
Moving is also a part of the language, but using it explicitly requires some understanding of a fundamental feature of C++ that was not usually talked about before: value categories.
Explanation of value categories
The names rvalues and lvalues historically refer to where the expression tends to be relative to an assignment operation. So for the expression:
x = 1+2;
The x
is an lvalue, and the 1+2 is an rvalue. The names can be slighltly
misleading, since an l-value could occur on either side of the assignment, and
there is no requirement that an assignment be made in every line of C, but every
expression (or sub-expression) can be classified under these two categories in
C++03. The difference is in memory; the L-value is given a real location in
memory, while the rvalue is temporary and cannot be used past the current
expression (and the compiler might optimize it away in some cases). An example
of an expression that is an rvalue is *(x+1)
, where x is a pointer. It is
quite possible for a function to return an lvalue.
In C++11, rvalues are further complicated because of the desire to move object. To properly grasp the syntax, it is important to understand all 3 unique categories in C++11, from the C++ standard n3055:
- lvalue: Anything that can be on the left side of equal sign.
- xvalue: An rvalue that about to expire (something being returned from a function, for example). An xvalue can be moved.
- prvalue: An rvalue that is not about to expire, like a literal (
12
,true
) or the result of a non-reference return of a function.
C++11 also defines two combination categories, glvalue is an lvalue or xvalue, and the classic rvalue, which is a prvalue or an xvalue.
Syntax for the value categories
Functions can return any of the three C++11 categories:
int returns_prvalue();
int& returns_lvalue();
int&& returns_xvalue();
And, functions can take either of the values:
void foo(int any_value);
void foo(int& lvalue);
void foo(int&& rvalue); // rvalue takes an rvalue, but inside the function it is a named lvalue
This hopefully gives you an idea of what a move function must look like. Its signature must be:
T&& move(T&& input_rvalue);
Here, it takes an rvalue and returns an xvalue. It is the same thing as doing a static cast to an rvalue. This xvalue is therefore movable by the compiler.
The most common uses tend to be in overloads, such as for move constructors, to specialize behavior for moves.
Other features
Tuples allow multiple return values, albeit through std::tie
. In C++17 they
are elevated to a more fundamental part of the language through new syntax.
C++ now supports user defined literals, for strings and for numbers. These were
not included in the standard library for common types until C++14, though. One
use was added, though; raw string literals R"(...)"
were added, with similar
meaning and semantics to the Python raw strings, were intended for the same use
(regular expressions).
Attributes were added, allowing arbitrary tags to be added to expressions. There are very few official ones, though some get added for each version. These are intended mostly for compiler specific functions, like OpenMP. They are indicated by double square brackets.
The parser is smarter, finally understanding <one, <two>>
correctly without
forcing a space between the greater-thans.
Suffix return type allows the return type to be listed after the function definition line, instead of before; this allows templated functions with complex return types to be written more easily, since the parameters exist after the definition line, but not before.
The override keyword indicates a function is intended to override a virtual function, making the intention clear (and compiler can check and issue errors). The final keyword is also added, enforcing a virtual function to be non-overridable.
Std library improvements
The powerful std::shared_ptr
and std::unique_ptr
remove most of the reasons
to fear pointers, though they don’t work that well with toolkits like ROOT that
try to do their own memory management. When the unique_ptr
or final
shared_ptr
go out of scope, the object they held will be cleaned up.
Dumb pointers
Thing* x = new Thing();
// Do stuff
delete x;
Smart pointers
std::unique_ptr<Thing> x{new Thing()};
// Do stuff
// Automatically deleted
You have std::make_shared<T>()
to help with making shared pointers without
using the dreaded new
keyword (the matching make_unique
was not added until
C++14).
A chrono library was added for consistent timekeeping on all platforms. A
threading library provides tools that work with the new functional tools and
makes threading easy, and also a mutex and atomic library to support it. A
regular expression library was added. More algorithms were added. A sized
integer library was finally added with cstdint
(a benefit of being based on
C99).
Several container libraries were added. The most notable container library
addition is the array library, which provides a compile time replacement for
C-arrays, with bounds features. Unordered versions of several containers were
added. Containers became much more powerful, now using move semantics, as well
as emplace_back
, which can construct a value directly inside a container to
avoid a copy. Most containers support initializer lists.
A functional library was added to give an explicit type to function pointers, and is used for lambdas as well. A bind function allows you to add function arguments to a function (currying in functional programming terms).
Type-based metaprogramming is supported through a type_traits
library.
An extensible random number library was added, including many useful distributions and several engines.
Further reading
There are many, many smaller features, as well as details of the larger features that were not covered here. See:
- Bjarne Stroustrup’s document - written by the creator of C++.
- SmartBear blog post
- ISO CPP
- Cpp Reference
- Wikipedia