声明:C++11 特性系列的笔记内容大多来自书籍《深入理解 C++11:C++11 新特性解析与应用》

_Pragma 操作符

_Pragma 操作符的功能与#pragma 的功能相同,用来向编译器传达语言标准之外的一些信息。 _Pragma 操作符的格式如下: _Pragma (字符串字面量)。 如 #pragma once 指示编译器头文件只编译一次。使用_Pragma:_Pragma (“once”)。 由于_Pragma 是一个操作符,类似 sizeof,可以把_Pragma 用于宏中。

预定义宏

C++11 增加了 C99 中一些宏的支持。

宏名称 功能
__STDC_HOSTED__ 编译器的目标系统环境是否有完整的标准 c 库,有定义为 1,否则定义为 0
__STDC__ 编译器的实现是否和 C 标准一致,宏是否定义以及定义的值由编译器决定
__STDC_VERSION__ 编译器支持的 C 标准版本,宏是否定义以及定义的值由编译器决定
__STDC_ISO_10646__ 表示 C++编译环境符合某个版本的 ISO/IEC 10646
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
#include <iostream>

using namespace std;

int main() {
    cout << "__STDC_HOSTED__" << __STDC_HOSTED__ << endl;
    cout << "__STDC__" << __STDC__ << endl;
    // cout << "__STDC_VERSION__" << __STDC_VERSION__ << endl; 这个宏没有定义
    cout << "__STDC_ISO_10646__" << __STDC_ISO_10646__ << endl;
}

__func__预定义标识符

func的基本功能是返回所在函数的名字。对于调试代码有十分重要的作用。在 C++11 中,允许它 出现在类或者结构体中。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
#include <iostream>

using namespace std;

class TestClass {
  public:
    TestClass() : name(__func__) {}

    const char* name;

};


int main() {
    TestClass tc;
    cout << tc.name << endl; // 输出结果为TestClass
}

变长参数的宏定义及VA_ARGS

变长参数的宏定义指在参数定义列表的最后一个参数为省略号,而VA_ARGS在实现部分替换省略号。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
#include <stdio.h>

#define LOG(...) {\
        fprintf(stderr, "%s: Line %d:\t", __FILE__, __LINE__);\
        fprintf(stderr, __VA_ARGS__);\
        fprintf(stderr, "\n");\
}

int main() {
    int x = 3;

    LOG("x = %d", x); //输出结果 test-2-3.cpp: Line 12: x = 3
}

连接宽窄字符

C++11 中,在将窄字符(char)和宽字符(wchar_t)连接时,编译器会将窄字符转换成宽字符, 再与宽字符进行连接。

long long 整型

C++11 加上了 long long 整型,各平台长度可以不同,但是至少有 64 位。 通过查看(或<limits.h>)中的宏可以了解平台上 long long 的大小。

扩展的整型

有些整型如__int16、int64_t 等。这些类型有的源于编译器扩展,有的来自某些编程环境等。 C++11 只定义了 5 种整型:signed char、short int、int、long int、 long long int 每种整型都有对应的无符号类型,有符号与无符号版本有相同的存储空间大小。

C++11 允许编译器扩展自有的整型,扩展的整型必须和标准类型一样,有符号类型和无符号类型占有 同样的空间大小。 整型提升的原则:

  • 长度越大等级越高
  • 长度相同,标准整型的等级高于扩展类型
  • 相同大小的有符号类型和无符号类型大小相同

宏__cplusplus

在 C++11 中__cplusplus 被预定义为 201103L。如果要代码使用支持 C++11 的编译器编译, 可以像下面这样检测:

1
2
3
#if __cplusplus < 201103L
    #error "should use c++11 implementation"
#endif

静态断言

静态断言也就是在编译期断言。在 C++11 中使用 static_assert 来实现。 static_assert 接受两个参数,一个是断言表达式,需要返回一个 bool 值;一个是警告信息。

1
2
3
4
5
static_assert(sizeof (int) == 8, "This 64-bit machine should follow this");

int main() {
    return 0;
}

static_assert 的断言表达式必须是在编译期可以计算的表达式。

noexcept 修饰符和 noexcept 操作符

C++11 中一个函数被 noexcept 表示它不会抛出异常。如果抛出了异常,编译器会终止程序的运行。 noexpect 修饰符有两种形式,一种是直接在函数后面加 noexpect, 还有一种是可以接受一个常量表达式作为参数。

1
2
3
void excpt_func() noexcept;

void excpt_func() noexcept (常量表达式);

不带常量表达式的 noexcept 就相当于 noexcept (true)

noexcept 作为一个操作符时通常可以用于模板中。如:

1
2
template <class T>
void fun() noexcept(noexcept (T()))

fun 是否是一个 noexcept 函数,由 T()表达式是否抛出异常决定。第二个 noexcept 就是一个 noexcept 操作符, 当其参数是一个有可能抛出异常的表达式时,其返回值为 false,反之为 true。

快速初始化成员变量

在 C++11 中,标准允许非静态成员变量的初始化有多种形式。除了初始化列表外,标准还允许使用等号或者 {}进行就地的非静态成员变量初始化。

1
2
3
4
struct init {
    int a = 1;
    double b { 1.2 };
};

对于非常量的静态成员,C++11 还是需要到头文件以外的地方定义它,这会保证编译时, 类静态成员的定义最后只存在于一个目标文件中。

非静态成员的 sizeof

在 C++98 中只有静态成员或者对象的实例才能对其成员进行 sizeof 操作 C++11 可以不用定义实例,直接获取非静态成员的大小。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
#include <iostream>
using namespace std;

class F {
  public:
    int n;
    static char ch;
};

int main() {

    F f;
    cout << sizeof (f.n) << endl; // C++98 通过, C++11 通过
    cout << sizeof (F::ch) << endl;   // C++98 通过, C++11 通过
    cout << sizeof (F::n) << endl;   // C++98 不通过, C++11 通过
}

扩展的 friend 语法

在 C++11 中,声明一个类为另一个类的友元时,不在需要使用 class 关键字了。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <iostream>
using namespace std;

class Poly;
typedef Poly P;

class A {
    friend class Poly; // C++98 通过, C++11通过
};

class B {
    friend Poly;  // C++98 不通过, C++11通过
};

class C {
    friend P; // C++98 不通过, C++11通过
};

int main() {

}

这个变化可以为类模板声明友元了,这在 C++98 中无法做到。

1
2
3
4
5
6
7
8
class P;

template <typename T> class People {
    friend T;
};

People<P> pp;  // 类型P是People的友元
People<int> pi;  // 对于int类型,友元声明被忽略

final/override 控制

final 能够阻止派生类重载父类的成员。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
class Object {
  public:
    virtual void fun();
};

class Base : public Object {
  public:
    void fun() final; // 声明为final
};

class Derived : public Base {
    void fun(); // 无法通过编译,以Base为基类的派生类都不能再重载void fun();
};

override 描述符表示一个函数是基类的重载版本,如果不是重载版本,编译器就会报错。

1
2
3
4
5
6
7
8
class Base {
    virtual void foo(int i);
};

class Derived : public Base {
    void foo(double i) override;
    // 编译错误,使用了override,说明此函数一定是基类函数的一个重载版本,但基类并没有对应的虚函数。
};

模板函数的默认模板参数

在 C++98 中模板类的声明可以有默认模板参数,但是函数不能有,C++11 模板函数也能有默认参数了:

1
2
3
4
template <class T = int>
class F{}; // C++98编译通过,C++11编译通过
template <typename T = int>
void foo(); // C++98编译失败,C++11编译通过

C++11 中类模板的默认参数必须从右往左定义,而函数模板的默认参数位置随意。

1
2
3
4
5
6
template <typename T1, typename T2 = int> class DefClass1;
template <typename T1 = int, typename T2> class DefClass2;  // 无法通过编译

template <typename T1 = int, typename T2> void DefFunc1(T1 a, T2 b);
template <int i = 0, typename T> void DefFunc2(T a);   // 函数模板,默认参数顺序无关

外部模板

外部(extern)的概念在 c 中就已经存在了,当我们在 a.c 中定义一个变量 i,在 b.c 中想使用它, 我们就需要在 b.c 中声明:

1
extern int i

这样生成的目标文件 a.o 和 b.o 就只有一份定义,不然就会产生两份定义,链接时就会出现错误。

对于函数模板也会有同样的问题,在不同文件中使用了模板函数,编译器会实作出两份一样的代码, 虽然编译器会删除一份,但是这会花很多时间。

外部模板的使用依赖于显示实例化,外部模板的声明在显示实例化前加一个关键字 extern,具体看示例代码。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
//在test.h中定义下面这个模板函数
template <typename T>
void fun(T) {}

//在a.cpp中定义下面的代码
#include "test.h"

template void fun<int>(int); // 显示地实例化

void test1() {
    fun(3);
}

// 在b.cpp中定义如下代码
#include "test.h"

extern template void fun<int>(int); // 在另一个文件使用同样的fun时,声明外部模板
void test2() {
    fun(4);
}

外部模板声明不能用于一个静态函数。

局部和匿名类型作为模板实参

在 C++98 中,局部类型和匿名的类型都不能做模板类的实参。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
template <typename T> class X { };
template <typename T> void TempFun(T t) { };
struct A { } a;
struct { int i; }b; // b是匿名类型变量
typedef struct { int i; }B;//B是匿名类型

void Fun() {

    struct C { }c;
    X<A> x1; // C++98通过, C++11通过
    X<B> x2;  // C++98错误,C++11通过
    X<C> x3; // C++98错误,C++11通过
    TempFun(a);// C++98通过,C++11通过
    TempFun(b); // C++98错误,C++11通过
    TempFun(c); // C++98错误,C++11通过
}

匿名类型的声明不能在模板实参的位置。

1
2
3
4
5
template <typename T> struct MyTemplate { };
int main() {
    MyTemplate<struct { int a; }> t;
    return 0;
}