“C++11 feels like a new language.” – Bjarne Stroustrup
The C++11 standard offers many useful new features. This page focuses specifically and only on those features that make C++11 really feel like a new language compared to C++98, because:
- They change the styles and idioms you’ll use when writing C++ code, often including the way you’ll design C++ libraries. For example, you’ll see more smart pointer parameters and return values, and functions that return big objects by value.
- They will be used so pervasively that you’ll probably see them in most code examples. For example, virtually every five-line modern C++ code example will say “auto” somewhere.
Use the other great C++11 features too. But get used to these ones first, because these are the pervasive ones that show why C++11 code is clean, safe, and fast – just as clean and safe as code written in any other modern mainstream language, and with C++’s traditional to-the-metal performance as strong as ever.
Notes:
- Like Strunk & White, this page is deliberately focused on brief summary guidance. It is not intended to provide exhaustive rationale and pro/con analysis; that will go into other articles.
- This is a living document. See the end for a list of the main changes and additions over time.
auto
Use auto wherever possible. It is useful for two reasons. First, most obviously it’s a convenience that lets us avoid repeating a type name that we already stated and the compiler already knows.
map< int ,string>::iterator i = m.begin(); |
double const xlimit = config[ "xlimit" ]; |
singleton& s = singleton::instance(); |
auto const xlimit = config[ "xlimit" ]; |
auto& s = singleton::instance(); |
Second, it’s more than just a convenience when a type has an unknown or unutterable name, such as the type of most lambda functions, that you couldn’t otherwise spell easily or at all.
binder2nd< greater > x = bind2nd( greater(), 42 ); |
auto x = []( int i) { return i > 42; }; |
Note that using auto doesn’t change the code’s meaning. The code is still statically typed, and the type of every expression is already crisp and clear; the language just no longer forces us to redundantly restate the type’s name.
Some people are initially afraid of using auto here, because it may feel like not (re)stating the type we want could mean we’ll get a different type by accident. If you want to explicitly enforce a type conversion, that’s okay; state the target type. The vast majority of the time, however, just use auto; it will rarely be the case that you get a different type by mistake, and even in those cases the language’s strong static typing means the compiler will usually let you know because you’ll be trying to call a member function the variable doesn’t have or otherwise use it as something that it isn’t.
Smart pointers: No delete
Always use the standard smart pointers, and non-owning raw pointers. Never use owning raw pointers and delete, except in rare cases when implementing your own low-level data structure (and even then keep that well encapsulated inside a class boundary).
If you know you’re the only owner of another object, use unique_ptr to express unique ownership. A “new T” expression should immediately initialize another object that owns it, typically a unique_ptr. A classic example is the Pimpl Idiom (see GotW #100):
widget::widget() : pimpl{ new impl{ } } { } |
Use shared_ptr to express shared ownership. Prefer to use make_shared to create shared objects efficiently.
widget* pw = new widget(); |
auto pw = make_shared<widget>(); |
Use weak_ptr to break cycles and express optionality (e.g., implementing an object cache).
If you know another object is going to outlive you and you want to observe it, use a (non-owning) raw pointer.
vector<unique_ptr<node>> children; |
nullptr
Always use nullptr for a null pointer value, never the literal 0 or the macro NULL which are ambiguous because they could be either an integer or a pointer.
Range for
The range-based for loop is a much more convenient way to visit every element of a range in order.
for ( vector< int >::iterator i = v.begin(); i != v.end(); ++i ) { |
Nonmember begin and end
Always use nonmember begin(x) and end(x) (not x.begin() and x.end()), because begin(x) and end(x) are extensible and can be adapted to work with all container types – even arrays – not just containers that follow the STL style of providing x.begin() and x.end() member functions.
If you’re using a non-STL collection type that provides iteration but not STL-style x.begin() and x.end(), you can often write your own non-member begin(x) and end(x) overloads for that type and then you can traverse collections of that type using the same coding style above as for STL containers. The standard has set the example: C arrays are such a type, and the standard provides begin and end for arrays.
sort( v.begin(), v.end() ); |
sort( &a[0], &a[0] + sizeof (a)/ sizeof (a[0]) ); |
sort( begin(v), end(v) ); |
sort( begin(a), end(a) ); |
Lambda Functions and Algorithms
Lambdas are a game-changer and will frequently change the way you write code to make it more elegant and faster. Lambdas make the existing STL algorithms roughly 100x more usable. Newer C++ libraries increasingly are designed assuming lambdas as available (e.g., PPL), and some even require you to write lambdas to use the library at all (e.g., C++ AMP).
Here’s one quick example: Find the first element in v that’s >x and <y. In C+11, the simplest and cleanest code is to use a standard algorithm.
vector< int >::iterator i = v.begin(); |
for ( ; i != v.end(); ++i ) { |
if ( *i > x && *i < y ) break ; |
auto i = find_if( begin(v), end(v), [=]( int i) { return i > x && i < y; } ); |
Want a loop or similar language feature that’s not actually in the language? No sweat; just write it as a template function (library algorithm), and thanks to lambdas you can use it with almost the same convenience as if it were a language feature, but with more flexibility because it really is a library and not a hardwired language feature.
lock_guard<mutex> hold { mut_x }; |
Get familiar with lambdas. You’ll use them a lot, and not just in C++ – they are already widely available and pervasively used in several popular mainstream languages. A good place to start is my talk Lambdas, Lambdas Everywhere at PDC 2010.
Move / &&
Move is best thought of as an optimization of copy, though it also enables other things like perfect forwarding.
Move semantics change the way we design our APIs. We’ll be designing for return by value a lot more often.
vector< int >* make_big_vector(); |
vector< int >* result = make_big_vector(); |
void make_big_vector( vector< int >& out ); |
make_big_vector( result ); |
vector< int > make_big_vector(); |
auto result = make_big_vector(); |
Enable move semantics for your type when you can do something more efficient than copy.
Uniform Initialization and Initializer Lists
What hasn’t changed: When initializing a local variable whose type is non-POD or auto, continue using the familiar = syntax without extra { } braces.
In other cases, including especially everywhere that you would have used ( ) parentheses when constructing an object, prefer using { } braces instead. Using braces avoids several potential problems: you can’t accidentally get narrowing conversions (e.g., float to int), you won’t occasionally accidentally have uninitialized POD member variables or arrays, and you’ll avoid the occasional C++98 surprise that your code compiles but actually declares a function rather than a variable because of a declaration ambiguity in C++’s grammar – what Scott Meyers famously calls “C++’s most vexing parse.” There’s nothing vexing about the new style.
rectangle w( origin(), extents() ); |
complex< double > c( 2.71828, 3.14159 ); |
int a[] = { 1, 2, 3, 4 }; |
for ( int i = 1; i <= 4; ++i ) v.push_back(i); |
rectangle w { origin(), extents() }; |
complex< double > c { 2.71828, 3.14159 }; |
vector< int > v { 1, 2, 3, 4 }; |
The new { } syntax works pretty much everywhere:
X::X( ) : mem1(init1), mem2(init2, init3) { } |
X::X( ) : mem1{init1}, mem2{init2, init3} { } |
Finally, sometimes it’s just convenient to pass function arguments without a type-named temporary:
void draw_rect( rectangle ); |
draw_rect( rectangle( myobj.origin, selection.extents ) ); |
draw_rect( { myobj.origin, selection.extents } ); |
The only place where I prefer not to write the braces is on simple initialization of a non-POD variable, like auto x = begin(v); , where it would make the code needlessly ugly because I know it’s a class type, so I know I don’t need to worry about accidental narrowing conversions, and modern compilers already routinely perform the optimization to elide the extra copy (or the extra move, if the type is move-enabled).
And More
There’s more to modern C++. Much more. And in the future I plan to write more in-depth pieces about these and other features of C++11 we’ll get to know and love.
But for now, this is the list of must-know features. These features form the core that defines modern C++ style, that make C++ code look and perform the way it does, that you’ll see used pervasively in nearly every piece of modern code you’ll see or write… and that make modern C++ the clean, and safe, and fast language that our industry will continue relying on heavily for years to come.
Major Change History
2011-10-30: Added C# lock example to lambdas. Reordered smart pointers to introduce unique_ptr first.
2011-11-01: Added uniform initialization.
No comments:
Post a Comment