C++ 新标准 11/14 语言新特性- 1
C++ Standard之演化
- C++ 98(1.0)
- C++ 03(TR1,technical report1)
- C++ 11(2.0)
- C++ 14
C++ 2.0新特性包含语言和标准库两个层面,后者以header files的形式呈现。
#include <type_traits>
#include <unordered_set>
#include <forward_list>
#include <array>
#include <tuple>
#include <regex>
#include <thread>
TR1版本中的很多特性是放在namespace std:tr1
下的(如shared _ptr、regex),现在也被放到了namespace std
里.
确认编译器是否支持C++11
编译器会定义一个__cplusplus
的常量,对于C++11是这样定义的#define __cplusplus 201103L
,对于C++98和C++03,则是#define __cplusplus 199711L
#include <iostream> using namespace std; int main() { cout << __cplusplus << endl; return 0; }
C++11里一些重要的内容
-
语言层面
- Variadic Templates
- move Semantics
- auto
- Rabge-base for loop
- Initializer list
- Lambdas
-
标准库层面
- type_traits
- Unordered容器
- forward_list
- array
- tuple
- Con-currency
- RegEx
Variadic Templates
在C语言的printf函数里面就有用到过...
,意为可以接受任意变化个数的参数。
int printf ( const char * format, ... );
… (additional arguments)
Depending on the format string, the function may expect a sequence of additional arguments, each containing a value to be used to replace a format specifier in the format string (or a pointer to a storage location, for n).
There should be at least as many of these arguments as the number of values specified in the format specifiers. Additional arguments are ignored by the function.——Cplusplus.comprintf
在C++2.0里...
也是用于表示一包(pack)东西。
- 用于template parameters,就是template parameters pack(模板参数包)
- 用于function parameter types,就是function parameter types pack(函数参数类型包)
- 用于function parameters,就是function parameters pack(函数参数包)
需要注意3处...
的位置(语法规定就那样写)。
// function 1 void printX() { } // function 2 template <typename T, typename... Types> void printX(const T& firstArg, const Types&... args) { cout << firstArg << endl; // print first argument printX(args...); // call printX() for remaining arguments } printX(7.5, "hello", bitset<16>(377), 42); //7.5 //hello //0000000101111001 //42
第二个函数里面的参数表示可以接受一个参数和一包参数,并且每个参数可以是不同数据类型的,可以用于做递归(将不定参数的数据一一分解)。
第一个函数(不带参数的)是必须的,因为最后会分解为一个参数和一个数量为零的包,后者调用不带参数的函数。
如果想知道这包参数(args)一共有多少个参数,则可以使用sizeof...(args)
来获取。
1 // function 3 2 template <typename...Types> 3 void printX (const Types&... args) 4 { /*...*/ }
问题:函数2和函数3可以共存吗?若可,谁比较泛化?谁比较特化?
函数2和函数3都是数量不定的模板(variadic template),不同的是函数2接受一个和一包参数,函数3接受一包参数。两者并不会引起ambiguous,经过测试会调用函数2。
Space in Template Expressions
vector<list<int> > // ok in each C++ version
vector<list<int>> // ok since C++11
nullptr and std::nullptr_t
C++11使用nullptr代替NULL(或0)表示空指针。
void f(int);
void f(void*);
f(0); // calls f(int)
f(NULL); // calls f(int) if NULL is 0, ambiguous otherwise
f(nullptr); // calls f(void*)
对于重载的f函数,按照之前NULL就是0的话,f(NULL)就会存在二义性。
Automatic Type Deduction with auto
以前auto用来描述本地变量,因为运行结束之后本地变量就会销毁,所以local变量也叫作auto变量。
C++11里面用auto来定义变量或类,编译器会自动判断数据类型。
auto i = 42; // i has type int
double f();
auto d = f(); // d has type double
编译器在编译的过程中知道什么东西是什么类型,也因此可以做模板的实参推导,所以在使用标准库中的算法时不需要指明每个参数的类型。auto主要用于过长或者过于复杂的数据类型,比如:
1 vector<string> v; 2 3 // ... 4 5 auto pos = v:begin(); // pos has type vector<string>::iterator 6 7 auto l = [](int x)->bool { // l has the type of a lambda 8 9 // ... // taking on int and returning a bool 10 11 }
Uniform Initialization
在C++11之前,程序员(特别是新手),很容易对初始化对象或变量时怎么写产生疑惑。初始化可能发生在小括号,大括号或者赋值运算符上。因此C++11引入一致性初始化(Uniform Initialization)的概念,任何初始化都可以使用共通的语法,即大括号。例如:
int values[] {1, 2, 3}; vector<int> v {2, 3, 5, 7, 11, 13, 17}; vector<string> cities { "Berlin", "New York", "London", "Braunschweig", "Cairo", "Cologne" }; complex<double> c {4.0, 3.0} // equivalent to c(4.0,3.0)
所有初始化都可以通过大括号完成,是如何实现的呢?
实际上,编译器看到{t1,t2... tn}
便做出一个initializer_list<T>
,它关联到一个array<T,n>
。调用函数(例如ctor)时,该array内的元素可被编译器逐一传给函数。但若调用的函数参数是一个initializer_list<T>
,则不会分解为数个类型为T的参数,而是直接将做出的initializer_list<T>
传入。
以上面的代码为例,{"Berlin", "New York", "London", "Braunschweig", "Cairo", "Cologne"}
形成一个initializer_list<string>
,背后有一个array<string,6>
。调用vector<string>
的ctors时编译器找到一个vector<string>
接受一个initializer_list<string>
,所有容器皆有如此的ctor。而{3.0,4.0}也形成一个initializer_list<double>
,背后有一个array<double,2>
。调用complex<double>
的ctor时该array内的2个元素被分解传给ctor。complex<double>
并无任何ctor接受initializer_list<T>
。
Initializer Lists
Initializer Lists也跟Variadic Templates一样可以用于不定个数的元素。
当使用大括号时,若未定义具体的值,则会初始化为0值,若数据类型是指针,则初始化为nullptr。
int i; // i has undefined value
int j{}; // j is initialized by 0
int* p; // p has undefined value
int* q{}; // q is initialized by nullptr
若声明的数据类型和大括号内的数据类型不匹配时,会自动转换类型,但缩窄范围(如把double数据赋值给int)的转换是不允许的。
int x1(5.3); // ok, but x1 becomes 5 int x2 = 5.3 // ok, but x2 becomes 5 int x3{5.3}; // error, narrowing int x4 = {5.3} // error, narrowing char c1{7}; // ok, even though 7 is an int, this is not narrowing char c2{9999}; // error, narrowing (if 9999 doesn't fit into a char) std::vector<int> v1 {1, 2, 4, 5} // ok std::vector<int> v2 {1, 2.3, 4, 5.6} // error, narrowing
Initializer Lists的背后通过initializer_list<>
实现。
void print (std::initializer_list<int> vals) { for (auto p = vals.begin(); p != vals.end(); ++p) { std::cout << *p << std::endl; } } print({2,3,5,7,11,13,17}); // pass a list of values to print()
传给initializer_list
者,一定必须也是个initializer_list
(or {...}
形式)。
当同时有特定个数的参数和initializer list作为参数时,会优先选用后者,测试代码如下:
class P { public: // ctor version 1 P(int a, int b) { cout << "P(int, int), a=" << a << ", b=" << b << endl; } // ctor version 2 P(initializer_list<int> initlist) { cout << "P(initializer_list<int>), values= "; for (auto i : initlist) cout << i << ' '; cout << endl; } }; P p(77,5); // P(int, int), a=77, b=5 P q {77,5}; // P(initializer_list<int>), values= 77 5 P r {77,5,42}; // P(initializer_list<int>), values= 77 5 42 P s = { 77, 5 }; // P(initializer_list<int>), values= 77 5
如果没有initializer list的版本(版本2),q和s将会调用版本1的构造函数,而r会报错。
Rvalue references
右值引用是C++03出现的一种新的引用类型,用于解决不必要的copy。当赋值的右边(拷贝来源端)是一个右值(rvalue),那么左边(拷贝接受端)的对象可以不重新分配内存,而直接“偷”右边对象的内容(这种情形只有在指针的情况下才会发生)。
- Lvalue:可以出现于operator=左侧者,如变量
- Rvalue:只能出现于operator=右侧者,如临时对象
// 以int试验 int a = 9; int b = 4; a = b; // ok b = a; // ok a = a+b; // ok a + b = 42; // Error: lvalue required as left operand of assignment // 以string试验 string s1("Hello"); string s2("World"); s1 + s2 = s2; // ok, s1+s2可以当做lvalue cout << "s1: " << s1 << endl; // s1: Hello cout << "s2: " << s2 << endl; // s2: World string() = "World"; // ok, 竟然可以对临时对象赋值 // 以complex试验 complex<int> c1(3,8), c2(1,0); c1 + c2 = complex<int>(4,9); // ok, c1+c2可以当做lvalue cout << "c1: " << c1 << endl; // c1:(3,8) cout << "c2: " << c2 << endl; // c2:(1,0) complex<int>() = complex<int>(4,9); // 竟然可以对临时对象赋值
函数的返回值是rvalue,对rvalue取地址是不允许的。
int foo() { return 5; } int x = foo(); // ok int* p = &foo(); // Error: 不能对rvalue取其地址 foo() = 7; // Error
对于临时对象(rvalue),可以通过右值引用(&&)避免重新分配内存创建对象;而对于lvalue,可以通过调用move()函数实现,但必须确保这个对象之后不会再被使用。
Perfect Forwarding
所谓完美的传递,指的是在多级函数调用的过程中,参数的属性(可变的还是const的,lvalue还是rvalue)也能够被传递。
void process(int& i) { cout << "process(int&):" << i << endl; } void process(int&& i) { cout << "process(int&&):" << i << endl; } void forward(int&& i) { cout << "forward(int&&):" << i << ", "; process(i); } int a = 0; process(a); // process(int&):0 // 视为lvalue处理 process(1); // process(int&&):1 // 临时对象视为rvalue处理 process(move(a)); // process(int&&):0 // 强制将lvalue改为rvalue forward(2); // forward(int&&):2, process(int&):2 // rvalue经由forward()传给另一个函数后却变为lvalue // (原因是在传递过程中把它变为了非临时对象) forward(move(a)); // forward(int&&):0, process(int&):0 //! forward(a); // Error: cannot bind 'int' lvalue to 'int&&' const int &b = 1; //! process(b); // Error: no matching function for call 'process(const int&) //! process(move(b)); // Error: no matching function for call to // 'process(std::remove_rederence<const int&>::type)' //! int& x(5); // Error: invaild initialization of non-const reference of // tyoe 'int&' from an rvalue of type 'int'
显然目前的写法并不能实现属性传递。所以标准库里面做move的操作之前会调用forward(),借用下面这段代码解决
// ...\4.9.2\include\c++\bits\move.h // @brief Forward an lvalue. // @return The parameter cast to the specified type. // This function is used to implement "perfect forwarding". template<typename _Tp> constexpr _Tp&& forward(typename std::remove_reference<_Tp>::type& __t) noexcept { return static_cast<_Tp&&>(__t); } // @brief Forward an rvalue. // @return The parameter cast to the specified type. // This function is used to implement "perfect forwarding". template<typename _Tp> constexpr _Tp&& forward(typename std::remove_reference<_Tp>::type&& __t) noexcept { static_assert(!std::is_lvalue_reference<_Tp>::value, "template argument" " substituting _Tp is an lvalue reference type"); return static_cast<_Tp&&>(__t); } // @brief Convert a value to an rvalue. // @param __t A thing of arbitrary type. // @return The parameter cast to an rvalue-reference to allow moving it. template<typename _Tp> constexpr typename std::remove_reference<_Tp>::type&& move(_Tp&& __t) noexcept { return static_cast<typename std::remove_reference<_Tp>::type&&>(__t); }
设计一个带有move的class
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 class MyString 2 { 3 private: 4 char* _data; 5 size_t _len; 6 void _init_data(const char *s) 7 { 8 _data = new char[_len+1]; 9 memcpy(_data, s, _len); 10 _data[_len] = '\0'; 11 } 12 public: 13 // default ctor 14 MyString() : _data(NULL), _len(0) { } 15 // ctor 16 MyString(const char* p) : _len(strlen(p)) 17 { 18 _init_data(p); 19 } 20 // copy ctor 21 MyString(const MyString& str) : _len(str._len) 22 { 23 cout << "Copy Constructor is called! source: " << str._data 24 << " [" << (void*)(str._data) << ']' << endl; 25 _init_data(str._data); // COPY 26 } 27 // move ctor, with "noexcept" 28 MyString(MyString&& str) noexcept : _data(str._data), _len(str._len) { 29 cout << "Move Constructor is called! source: " << str._data 30 << " [" << (void*)(str._data) << ']' << endl; 31 str._len = 0; 32 str._data = NULL; // 这句很重要!!! 33 // 另外需要避免 delete (in dtor),不然会把临时对象杀掉 34 } 35 36 // copy assignment 37 MyString& operator=(const MyString& str) 38 { 39 cout << "Copy Assignment is called! source: " << str._data 40 << " [" << (void*)(str._data) << ']' << endl; 41 if (this != &str) { 42 if (_data) delete _data; 43 _len = str._len; 44 _init_data(str._data); // COPY! 45 } 46 else { 47 cout << "Self Assignment, Nothing to do." << endl; 48 } 49 return *this; 50 } 51 // move assignment 52 MyString& operator=(MyString&& str) noexcept 53 { 54 // 注意 noexcept 55 cout << "Move Assignment is called! source: " << str._data 56 << " [" << (void*)(str._data) << ']' << endl; 57 if (this != &str) 58 { 59 if (_data) delete _data; 60 _len = str._len; 61 _data = str._data; // MOVE! 62 str._len = 0; 63 str._data = NULL; // 这句很重要!!! 64 // 另外需要避免 delete (in dtor),不然会把临时对象杀掉 65 } 66 return *this; 67 } 68 69 // dtor 70 virtual ~MyString() 71 { 72 cout << "Destructor is called! " << "source: "; 73 if (_data) 74 cout << _data; 75 cout << " [" << (void*)(_data) << ']' << endl; 76 77 if (_data) 78 { 79 delete _data; 80 } 81 } 82 };