What are the new features, 11 and new features of C ++ 11 compared with C ++ 98?
This article is a translation of the following blog:
Https://herbsutter.com/elements-of-modern-c-style/
The C ++ 11 standard provides many useful new features. This article specifically targets the features that make C ++ 11 and C ++ 98 look like a new language, because:
- These features of C ++ 11 have changed the style and habits of Writing C ++ code, and have also changed the way C ++ libraries are designed. For example, you will see more smart pointers that are used as parameters and return values, and functions that return huge objects by value.
- They are widely used and can be seen in most code. For example, you can see the auto keyword in almost every five lines of C ++ code in modern C ++.
There are some other very good new features in C ++ 11, but first familiarize yourself with the new features described in this article, these widely used features demonstrate why C ++ 11 code is concise, secure, and fast, just like other mainstream modern development languages, and the performance is as powerful as traditional C ++.
1. Auto
Use auto whenever possible. There are two reasons. First, it is very obvious that it is very convenient to avoid repeated input of type names that we have already declared and recognized by the compiler.
1 // C++98 2 3 map<int,string>::iterator i = m.begin(); 4 5 double const xlimit = config["xlimit"]; 6 7 singleton& s = singleton::instance(); 8 9 // C++1110 11 auto i = begin(m);12 13 auto const xlimit = config["xlimit"];14 15 auto& s = singleton::instance();
Second, when you encounter a type that you do not know or cannot express in languages, auto is not just easy to use. For example, the type of most lambda functions, you cannot easily spell out the type or even write it out.
// C++98binder2nd< greater > x = bind2nd( greater(), 42 ); // C++11auto x = [](int i) { return i > 42; };
Note that using auto does not modify the semantics of the Code. The code is still static input (statically typed), and each expression is neat; it just does not force us to re-declare the type name.
Some people are afraid to use auto at the beginning, because they feel like they have not declared (re-declare) the type we want, which means we may suddenly get a different type. If you want to display a forced type conversion, this is no problem; declare the target type. However, in most cases, using auto is sufficient. It is rare to obtain another type due to an error. In the case of strong static typing, if the type is incorrect, the compiler will tell you.
2. smart pointer, no delete
Always use smart pointers instead of native pointers and delete. Unless you need to implement your own underlying data structure (encapsulate native pointers in class boundary)
If you know that you are the only owner of another object, use unique_ptr to indicate the unique ownership. A "new T" expression can quickly Initialize an object with this smart pointer, especially unique_ptr. A typical example is to point to the Implementation pointer (Pimpl Idiom ):
1 // C++11 Pimpl idiom: header file 2 class widget { 3 public: 4 widget(); 5 // ... (see GotW #100) ... 6 private: 7 class impl; 8 unique_ptr<impl> pimpl; 9 };10 11 // implementation file12 class widget::impl { /*...*/ };13 14 widget::widget() : pimpl{ new impl{ /*...*/ } } { }15 // ...
Shared_ptr is used to represent shared ownership ). It is better to use make_shared to create shared objects.
1 // C++982 widget* pw = new widget();3 :::4 delete pw;5 6 // C++117 auto pw = make_shared<widget>();
Use weak_ptr to break the loop and represent optionality (for example, implementing an object cache)
1 // C++11 2 class gadget; 3 4 class widget { 5 private: 6 shared_ptr<gadget> g; // if shared ownership 7 }; 8 9 class gadget {10 private:11 weak_ptr<widget> w;12 };
If you know that another object is longer than your life cycle and you want to observe this object, use the native pointer (raw pointer ).
1 // C++112 class node {3 vector<unique_ptr<node>> children;4 node* parent;5 public:6 :::7 };
3. Nullptr
Nullptr is used to represent a NULL pointer. Do not use the number 0 or macro NULL to represent NULL pointers because these are ambiguous and can represent both integer and pointer.
1 // C++982 int* p = 0;3 4 // C++115 int* p = nullptr;
4. Range
Sequential access to elements within a range is more convenient for a range-based for loop.
1 // C++982 for( vector<int>::iterator i = v.begin(); i != v.end(); ++i ) {3 total += *i;4 }5 6 // C++117 for( auto d : v ) {8 total += d;9 }
5. Non-member begin and end
Use non-member functions begin (x) and end (x) (not x. begin () and x. end (), because begin (x) and end (x) are eXtensible, it can work together with all container types-or even an array-not just for x that provides the STL style. begin () and x. end () member function container.
If you are using a non-STL set type, this type provides the iterator but is not an STL-style x. begin () and x. end (), you can overload his non-member functions begin () and end (), so that you can use the same style as STL container for encoding. An example is given in the standard: array, and the begin and end functions of the object are provided:
1 vector<int> v; 2 int a[100]; 3 4 // C++98 5 sort( v.begin(), v.end() ); 6 sort( &a[0], &a[0] + sizeof(a)/sizeof(a[0]) ); 7 8 // C++11 9 sort( begin(v), end(v) );10 sort( begin(a), end(a) );
6. Lambda functions and Algorithms
Lambda expressions change the rules of the game and change your encoding method from time to time. This method is elegant and fast. Lambda improves the practicability of existing STL algorithms by a hundred times.
The newly added C ++ library is designed to support lambad expressions (for example, PPL). Some libraries even need to be used by writing lambda expressions (for example: c ++ ).
Here is an example of finding the first element in v> X and <Y. In C ++ 11, the simplest and cleaner code is to use standard algorithms.
1 // C++98: write a naked loop (using std::find_if is impractically difficult)2 vector<int>::iterator i = v.begin(); // because we need to use i later3 for( ; i != v.end(); ++i ) {4 if( *i > x && *i < y ) break;5 }6 7 // C++11: use std::find_if8 auto i = find_if( begin(v), end(v), [=](int i) { return i > x && i < y; } );
What should I do if I want to use a loop or a similar language feature that does not actually exist in this language? The ready-made template functions (library algorithms) can be used. Thanks to lambda, it is as convenient as using a language, but more flexible, because it is indeed a library rather than a fixed language feature.
1 // C# 2 lock( mut_x ) { 3 ... use x ... 4 } 5 6 // C++11 without lambdas: already nice, and more flexible (e.g., can use timeouts, other options) 7 { 8 lock_guard<mutex> hold { mut_x }; 9 ... use x ...10 }11 12 // C++11 with lambdas, and a helper algorithm: C# syntax in C++13 // Algorithm: template<typename T> void lock( T& t, F f ) { lock_guard hold(t); f(); }14 lock( mut_x, [&]{15 ... use x ...16 });
Familiar with lambda, you will find that they are very useful, not just in c ++, they have been supported and popular in several mainstream languages.
7. Move /&&
Moving is the most appropriate Optimization for copying, although it also contains other things (like perfect forwarding ))
The move semantics changes the way we design APIs. We will design functions more and more into return by value.
1 // C++98: alternatives to avoid copying 2 vector<int>* make_big_vector(); // option 1: return by pointer: no copy, but don't forget to delete 3 ::: 4 vector<int>* result = make_big_vector(); 5 6 void make_big_vector( vector<int>& out ); // option 2: pass out by reference: no copy, but caller needs a named object 7 ::: 8 vector<int> result; 9 make_big_vector( result );10 11 // C++11: move12 vector<int> make_big_vector(); // usually sufficient for 'callee-allocated out' situations13 :::14 auto result = make_big_vector(); // guaranteed not to copy the vector
If you want to obtain a method that is more efficient than copy, use the move syntax for your type.
8. Unified initialization and initialization list
Unchanged: When initializing a non-POD or auto local variable, continue to use the familiar = syntax without extra curly braces.
1 // C++98 or C++112 int a = 42; // still fine, as always3 4 // C++ 115 auto x = begin(v); // no narrowing or non-initialization is possible
In other cases (especially the ubiquitous use () to construct objects), it would be better to use curly braces. Using curly braces {} can avoid some potential problems: You won't suddenly get a value after the narrowing conversions (for example, float is converted to int ), there will be no occasional uninitialized POD member variables or arrays, and you can also avoid the strange thing in c ++ 98: Your code compilation is okay, what you need is a variable, but you actually declare a function, which all comes from the ambiguity of the C ++ declaration syntax. Scott Meyers's famous saying: "C ++'s most distressing analysis ". By using the new style syntax, the above parsing problems will no longer exist.
1 // C++98 2 rectangle w( origin(), extents() ); // oops, declares a function, if origin and extents are types 3 complex<double> c( 2.71828, 3.14159 ); 4 int a[] = { 1, 2, 3, 4 }; 5 vector<int> v; 6 for( int i = 1; i <= 4; ++i ) v.push_back(i); 7 8 // C++11 9 rectangle w { origin(), extents() };10 complex<double> c { 2.71828, 3.14159 };11 int a[] { 1, 2, 3, 4 };12 vector<int> v { 1, 2, 3, 4 };
The new {} syntax works well almost anywhere.
1 // C++982 X::X( /*...*/ ) : mem1(init1), mem2(init2, init3) { /*...*/ }3 4 // C++115 X::X( /*...*/ ) : mem1{init1}, mem2{init2, init3} { /*...*/ }
Finally, it is convenient to pass function parameters without (type-named temporary:
Void draw_rect (rectangle );
1 // C++982 draw_rect( rectangle( myobj.origin, selection.extents ) );3 4 // C++115 draw_rect( { myobj.origin, selection.extents } );
I do not like to use curly braces {}. The only difference is that when initializing a non-POD variable, such as auto x = begin (v); using curly braces will make the code ugly, as I know it is a class type, I don't have to worry about shrinking the conversion, and the modern compiler has already copied additional (or extra move, if the type is move-enabled) optimized.