C++11新特性

右值引用和移动构造

通常,只有左值能够绑定在非常量引用上。函数返回的右值一般是临时值,如果不被引用绑定,则会在函数调用的语句执行完后马上被销毁。但是如果使用一个常量引用,可以引用这个临时值,从而使得临时值的生命期延长。

C++11后,加入了新的右值引用。可以直接把右值绑定在右值引用,最广泛的运用就是绑定函数返回的临时值。右值引用通过两个&进行声明,如A &&a = getA();。如果直接把函数的返回值作为参数再传给其他函数,则优先匹配右值引用参数的函数版本,移动构造函数便是一个例子:

#include <iostream>

using namespace std;

class MyArray {
    int size;
    int *arr;
public:
    MyArray() : size(0), arr(NULL) {}

    MyArray(int sz) :
            size(sz), arr(new int[sz]) {
        //init array here…
    }

    MyArray(const MyArray &other) :
            size(other.size),
            arr(new int[other.size]) {
        for (int i = 0; i < size; i++) {
            arr[i] = other.arr[i];
        }
    }

    int get_size() const { return size; }

    // 移动构造函数
    MyArray(MyArray &&other) :
            size(other.size), arr(other.arr) {
        other.arr = NULL;
    }

    ~ MyArray() {
        delete[] arr;
    }
};

MyArray change_aw(const MyArray &other) {
    //这里aw是一个函数内的局部变量
    MyArray aw(other.get_size());
    //Do some change to aw.

    // 函数返回局部变量,这里会有第一次拷贝。
    return aw;
}

int main(){
    MyArray array(5);

    // 如果没有移动构造函数,这里调用拷贝构造,发生第二次拷贝
    // 如果有移动构造函数,这里会优先匹配右值引用参数的移动构造函数,不会有第二次拷贝
    MyArray array1=change_aw(array);
}

外部模板

外部模板是指,在本模块中使用到的模板的一个实例在其他模块中有已经实例化的版本了,可以声明为外部模板,避免这里再次重复实例化。

使用类似extern template void myfunc<int>(int);的语法声明一个模板的实例为外部已经存在的。编译器不会再实例化这个这个版本的模板。但是如果实际上并没有被实例化而做了错误的声明则会报错。

常量表达式

C++11之前,像switch的case之类的地方应该填入常量,如果填入一个表达式是不能过编译的。但是有一些表达式看似需要计算,其实结果在编译期即可确定。因此C++11之后提供了常量表达式的声明。声明为常量表达式之后,表达式的结果可以当做常量使用,编译器会尝试在编译期计算结果并且绑定为常量。但是如果将在编译器无法确定值,则会报错。

例如:

enum Flags {
    GOOD = 0, FAIL = 1, BAD = 2, EOF = 3
};

// 常量表达式定义
constexpr int operator|(Flags f1, Flags f2) { return Flags(int(f1) | int(f2)); }

void f(Flags x) {
    switch (x) {
        case BAD: /* ... */break;
        case FAIL: /* ... */ break;
        case BAD|EOF: /* ... */ break; //常量表达式可以这样使用
        default: /* ... */ break;
    }
}

Lambda函数

匿名函数。使用[](<参数列表>){函数体}类似的方式声明最普通的lambda函数。另外C++11的Lambda函数还可以直接使用上下文中的变量,不仅限于参数传入的那些,只需要进行简单的声明即可。详细如下:
| 写法 | 捕获上下文变量的形式|
|—|—|
| [] | Capture nothing 不捕获,不能使用上下文中的变量 |
| [&] | Capture any referenced variable by reference 以引用的形式捕获 |
| [=] | Capture any referenced variable by making a copy 以拷贝的形式捕获 |
| [=, &foo] | Capture any referenced variable by making a copy, but capture variable foo by reference 只有捕获foo变量时使用引用,其他都拷贝 |
| [bar] | Capture bar by making a copy; don’t copy anything else 只通过拷贝捕获bar变量 |

使用的方法如:

#include <iostream>
#include <functional>
#include <string>
#include <vector>
using namespace std;

vector <string> str_filter(vector <string> &vec, const function<bool(string & )>& matched) {
    vector <string> result;
    for (string tmp : vec) {
        if (matched(tmp))
            result.push_back(tmp);
    }
    return result;
}

int main() {
    vector <string> vec = {"www.baidu.com", "www.kernel.org", "www.google.com"};
    string pattern = ".com";
    //下面的语句使用一个lambda函数
    vector <string> filterd = str_filter(vec,
                                         [&](string &str) {
                                             return str.find(pattern) != string::npos;
                                         });
}

统一初始化

声明数组的时候可以用如int a[]={1,2,3}的形式初始化,那这种方便的形式能同样用来初始化对象就好了。C++11中加入了这个新特性,允许自定义统一初始化的构造器并在代码中使用来初始化对象。

#include <list>
#include <vector>
#include <initializer_list>
using namespace std;

class A{
    int x, y, z;
    //Default generated by compiler
public:
    A(initializer_list<int> list) {
        // auto可以自动指定类型
        auto it = list.begin();
        x = *it++;
        y = *it++;
        z = *it;
    }
};

int main(){
    int arr[] = {1, 2, 3}; // 数组的初始化
    vector<int> vec = {1, 2, 3}; // vector也可以这样初始化
    A  a = {1, 2, 3};  // 现在自定义的类也可以这样初始化了
}

空指针nullptr

之前都使用NULL表示空指针,但是由于NULL其实是一个整数0,所以可以会造成歧义。C++11之后可以使用专门的nullptr赋值给指针,使其成为空指针。这样可以避免产生歧义。nullptr实际上是nullptr_t类型的,和整数0以及通过宏定义的NULL有所区别。