请稍侯

C++11新特性

04 July 2016

C++11是曾经被叫做C++0x,是对目前C++语言的扩展和修正,C++11不仅包含核心语言的新机能,而且扩 展了C++的标准程序库(STL),并入了大部分的C++ Technical Report 1(TR1)程序库(数学的特殊 函数除外)。

C++11包括大量的新特性:包括lambda表达式,类型推导关键字auto、decltype,和模板的大量改进。 编译器通过添加 -std=c++11 或者 -std=c++0x 参数来支持C++11。

核心语言运行时性能强化

以下特性基本上用于提升程序运行时的性能。

右值引用和move语义

右值引用(R-values)是C++11标准中一个令人难以捉摸的特性。在C语言时代,左值和右值的定义如下: 左值是一个可以出现在赋值运算符左边或者右边的表达式exp,而右值是只能出现在右边的表达式。 例如:

int a = 1;  
int b = 2;
// a和b是左值
a = b;  // ok
b = a;  // ok

// a * b是右值
a * b = 11;   // error

在C++中由于自定义类型的引入使得上述的定义变得不那么准确,另一个较好的定义左值是一个指向某内存 空间的表达式,并且我们可以用&操作符获得该内存空间的地址,右值就是非左值的表达式。

int a = 1;  // ok
int *ptr = &a; // ok
int& func();
func() = 42;  // ok
// ptr, a和func()都是左值

int rfunc();
int *ptr1 = &rfunc(); // error rfunc()是右值
int *ptr = &42; // error 42是右值

右值引用的加入主要是为了解决move语义和完美转发。

(未完待续)

常量表达式constexpr

C++11中引入了新的关键字constexpr,其语义是“常量表达式”,即在编译期间可以求值的表达式。如:

constexpr int func(int t) {
    return t + 1;
}

constexpr int a = func(1);  // ok
constexpr int b = func(cin.get()); // error
constexpr int c = a * 2 + 3; // ok

constexpr还可以用于修饰类的构造函数,在保证如果提供给构造函数的参数都是constexpr的前提下,那 么产生的对象中的所有成员都会是constexpr,可用于各种只能使用constexpr对象的场合。

核心语言编译期间的加强

外部模板

在标准C++中,在编译过程中需要对出现的每一处模板进行实例化;链接时,链接器还要移除重复的实例化代 码,有时候这会大大增加编译和链接的时间。而在C++中已经有强制编译器在特定位置进行实例化的语法了:

template class std::vector<MyClass>;

在C++11中简单地加入了外部模板的语法:

extern template class std::vector<MyClass>;

例如:

// func.h
template <typename T>
void func(T t) { }

// src1.cpp
void test0() {
    func<int>(1);
}

// src2.cpp
extern template void func<int>(int);
void test1() {
    func<int>(1);
}   

核心语言使用性的加强

统一初始化语法

C++11用大括号统一了初始化的方法。

对于POD类型(Plain Old Data):

int arr[3] = {1, 2, 3};
struct tm today = {0};

在类(class/struct)是极简的、属于标准布局,以及它的所有非静态变量成员都是POD时,会被识别为POD。

struct A { int m; }; // POD
struct B { ~B(); int m; }; // non-POD
struct C { C() : m() {}; ~(); int m; }; // non-POD

对于POD类型如上述中类A, new Anew A()是不一样的,前者m没有被初始化话,后者被初始化了。 而对于non-POD类型,两种方式m都会被初始化。因此,在C++11中对初始化进行了统一。

int * nums = new int[3]{1, 2, 3}; // C++11 only
class X {
  private:
    int nums_[4];
  public:
    X() : nums_{1, 2, 3, 4} {} // C++11 only
};
   
vector<string> vs = {"first", "second", "third"};
map<string, string> user = {
    {"Lady Gaga", "123456789"},
    {"Michael Joseph", "987654321"}};

类型推导

在标准C++和C中,使用变量定义时必须指明其类型。但是在模板编程中有时候函数的返回值难以确定,为了 解决上述的问题,C++11引入了两个新的关键字: auto和decltype。 auto的示例如下:

auto a = 0;   // int
auto b = 'b'; // char
vecotr<string> vs;
auto it = vs.begin(); // vector<string>::const_iterator 

decltype用于评估括号内的类型,对于decltype(expr)其规则如下:

  1. 如果表达式expr是一个变量,那么代表该变量的类型。
  2. 如果表达式expr是一个函数,那么代表函数返回值的类型。
  3. 如果不符合1和2,如果expr是左值,类型为T,那么decltype(e)是T&;如果是右值,则是T。

示例如下:

template <typename X, typename Y>
auto z = [](X x, Y y)->decltype(x+y) { return x + y;}

基于范围的for循环

直接上示例:

int arr[3] = {1, 2, 3};
for(const int& e : arr) {
    std::cout << e << std::endl;
}

for(auto& e : arr) { // or auto& e => int& e
    e &= 0xFF;
}

Lambda 表达式

C++11中引入了Lambda表达式,既匿名函数。其语法如下

[capture](parameters) -> return_type { body }
[]  // body中不能引用外部变量
[=] // body中外部变量以值传递的形式传入
[&] // body中外部变量以引用的形式传入
[x, &y]  // body中x值传递的形式传入,y为引用传入
[&, x] // body中除了x为值传递外其他都是引用传递
[=, &x] // body除x为引用传递外,其他外部变量都是值传递
->return_type // 为可选项

示例:

int sum = 0;
vector<int> v{1, 2, 3};
for_each(v.begin(), v.end(), [&sum](int x) {
    sum += x;
});

返回值后置的函数定义

在C++03中一下代码是不合法的,因为ReturnType的类型必须在x+y的结果出来后才能确定。

template <typename X, typename Y>
ReturnType Func(const X& x, const Y& y) { 
    return x + y; // ReturnType 必须是x + y类型
} 

template <typename X, typename Y>
decltype(x+y) Func(const X& x, const Y& y) { 
    return x + y;
} // error: x和y的定义编译器在分析到函数原型的后半部分时才出现

C++11引入了一种新的函数定义声明语法:

template <typename X, typename Y>
auto Func(const X& x, const Y& y) -> decltype(x+y) { 
    return x + y; // ok
} 

委托构造

在以前版本的C++中,构造函数之间不能相互调用。如果要写内容相似的构造函数就必须把相同的内容放到私 有的成员函数中,并且基类的构造函数不能直接成为派生类的构造函数,即使它已经够用了。C++11中允许构 函数调用其他构造函数,称为委托构造。

class User {
  public:
    User() : User("None", 0){}
    User(string name) : User(name, 0) {}
    User(string name, ulong id) : name_(name), id_(id) {}

  private:
    string name_;
    ulong  id_;
};

class Student : public User{
  public:
    using User::User;

  private:
   string school = "xmu"; // c++11 only
};

虚函数重载

在C++里面,派生类容易意外地重载虚函数,比如说

class Base {
  public:
    virtual void Func();
};

class Derived: Base {
  public:
    void Func();
};

此时如果基类Base中的虚函数签名被改变,那么派生类将不再重载虚函数,程序员很可能不会注意到这个问 题,从而导致错误的调用。

C++11中加入override来防止上述情况的发生,并在编译期间捕获此类错误,该功能是向下兼容的。

class Base {
  public:
    virtual void Func();
};

class Derived: Base {
  public:
    virtual void Func() override; //ok
    virtual void Func(int) override; // error
};

C++11中还提供了关键字final来防止类被继承或是函数被重载。

class Base final {};
class Derived : public Base {}; // error: Base is marked as final

class User {
  public:
    virtual void Func() final;
};

class Student {
  public:
    virtual void Func();  // error
};

空指针nullptr

在C语言中空指针NULL被定义为((void*)0)或是0。在C++中不允许void*隐式转换为别的类型的指针, 为了使代码char *ptr = NULL通过编译,NULL只能被设为0。这就出现了一些无法区分代码语义的情况。

void Func(char *);
void Func(int);

当你调用Func(NULL)时,想调用上者,实际上将调用的却是Func(int)函数。C++11中引入的空指针nullptr, nullptr的类型为nullptr_t,可以隐式地转换成任何指针类型,也可以进行相等或不等的比较,但不能隐 式转换成整数,也不能与整数进行比较。

强枚举类型

在标准C++中,枚举类型不是类型安全的。枚举类型被视为整型,这使得不同类型之间可以比较。此外枚举类型 所使用的整型的类型无法确定。最重要的是枚举类型名暴露在一般范围中,两个不同的枚举类型不能拥有相同的 枚举名。

enum Side{ LEFT, RIGHT};
enum Dir { UP, DOWN, LEFT, RIGHT}; // error

C++11中需要为枚举类型提供类型,默认为int类型。

enum Enum1;  // error
enum Enum2 : unsigned int; // c++11 only
enum class Enum3; // c++11 only, default(int)
enum class Enum4 : unsigned int; // c++11 only

角括号修正

C++11的编译器可以更好地解析右角括号的语法

std::vector<vector<int>> v; // error with C++03 but ok with C++11

核心语言能力的提升