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

 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 };
MyString

 

posted on 2018-11-30 22:53  flysong  阅读(853)  评论(0编辑  收藏  举报

导航