nullptr (C++11)

nullptr 专门用于表示空指针,旨在解决传统 NULL 或 0 在类型安全性和代码清晰度上的缺陷。

nullptr 的类型为 std::nullptr_t,只能隐式转换为指针类型(包括原生指针、智能指针、托管句柄等),​不能转换为整数类型,避免了与整型 0 的混淆。

而传统 NULL 是宏(通常定义为 0 或 (void*)0),可能被误解释为整数,导致类型错误。

在函数重载场景中,nullptr 可明确选择指针版本的重载函数,避免因 NULL 被解释为整数导致的错误调用:

1
2
3
4
void foo(int);        // 整型版本
void foo(int*); // 指针版本

foo(nullptr); // 正确调用指针版本

而且在模板中可精确匹配指针类型,避免推导为整数:

1
2
3
4
5
6
7
8
9
10
#include <iostream>

template<typename T>
void check(T ptr) {
std::cout << "ptr type: " << typeid(ptr).name() << std::endl;
}

int main() {
check(nullptr); // ptr type: std::nullptr_t
}

override/final (C++11)

override 是一个用于显式标记派生类中重写(Override)基类虚函数的关键字。它通过强制编译器检查和增强代码可读性的方式,显著提升了继承体系的安全性和可维护性。

override 明确告知编译器和开发者:当前函数旨在重写基类的虚函数,若派生类函数签名(参数类型、const 限定、引用限定等)与基类虚函数不完全匹配,编译器会立即报错,避免隐蔽的错误。

final 关键字提供两种限制能力:

  • ​禁止类被继承

    标记为 final 的类不可作为基类,编译时会阻止派生类继承它。

    1
    2
    class Base final { /* ... */ }; // Base 类不可被继承
    class Derived : public Base { }; // ❌ 编译错误:Base 是 final 的
  • 禁止虚函数被重写

    标记为 final 的虚函数在派生类中不可被覆盖,确保基类设计意图不被破坏。

    1
    2
    3
    4
    5
    6
    7
    8
    class Base {
    public:
    virtual void func() final; // func 不可被重写
    };
    class Derived : public Base {
    public:
    void func() override; // ❌ 编译错误:func 是 final 的
    };

final 常与 override 配合使用:

  • override:确保派生类函数正确重写基类虚函数(签名不匹配时报错)。
  • final:在派生类中进一步禁止后续重写。
1
2
3
4
class Derived : public Base {
public:
void func() override final; // ✅ 重写基类函数,并禁止后续覆盖
};

constexpr (C++11)

constexpr 用于在编译时计算常量表达式,提升程序性能和安全性。其核心机制是将计算从运行时转移到编译时,减少运行时开销并支持编译期逻辑验证。

下面是一个在编译器计算斐波那契数列的例子:

1
2
3
4
constexpr int factorial(int n) { 
return (n <= 1) ? 1 : n * factorial(n - 1);
}
constexpr int result = factorial(5); // 编译时计算结果为 120

众所周知,在声明数组时,数组大小需要为常量,使用 constexpr 可以通过下面方式声明数组:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
constexpr int getArraySize(int type) {
// 需要C++14支持
if (type == 1) {
return 42;
}
else if (type == 2) {
return 52;
}
return 10;
}

int main()
{
int array[getArraySize(1)];
}

在使用 constexpr 时,有以下注意事项与技巧:

  • 必须用常量表达式初始化(字面值、其他 constexpr 变量等)。
  • constexpr 隐含 const 属性,也是常量,不可修改。
  • C++11 ​中函数体仅能包含单条 return 语句,但 ​C++14 以上​​可以支持循环、局部变量、条件分支、多个return等复杂逻辑。
  • 使用 constexpr 声明的函数,仍然可以在运行时作为普通函数调用。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    constexpr int getArraySize(int type) {
    if (type == 1) {
    return 42;
    }
    else if (type == 2) {
    return 52;
    }
    return 10;
    }

    int main() {
    int array[getArraySize(1)]; // 这里的 getArraySize(1) 在编译时被求值,返回 42

    int j = 2;

    std::cout << getArraySize(j) << std::endl; // 这里的 getArraySize(j) 在运行时被求值,返回 52
    }
  • constexpr 也不是万能的灵丹妙药,在处理复杂计算时还需权衡编译时间成本。
  • 可以结合使用 static_assert 来验证编译期结果,如:static_assert(factorial(5) == 120, "Error");

auto (C++11)

auto 用于在编译期根据初始化表达式自动推导变量类型,可以简化代码并提升可读性。

使用auto声明的变量​必须初始化。

1
2
3
auto x = 42;        // 推导为 int
auto y = 3.14; // 推导为 double
auto name = "John"; // 推导为 const char*

decltype (C++11)

decltype用于查询表达式的类型,发生在编译期,且不会对表达式进行求值。

1
2
3
4
5
int i = 4;
decltype(i) a; // a 的类型为 int

std::vector<int> vec;
typedef decltype(vec.begin()) vectype; // 推导迭代器类型

decltype在泛型编程中常用于追踪函数返回类型:

1
2
3
4
template <typename T, typename U>
auto add(T t, U u) -> decltype(t + u) {
return t + u;
}

thread_local (C++11)

thread_local用于声明线程局部存储变量,每个线程都有该变量的独立实例,线程之间不会相互干扰,从而无需额外的同步机制(如互斥锁)就能实现线程安全。

1
thread_local int counter = 0; // 每个线程有自己的 counter

根据声明thread_local变量时的作用域的不同,变量有不同的特性。

在全局作用域下声明的thread_local变量,变量是全局的,且每个线程都有该变量独立的副本。无论线程是否访问了该变量,都会在线程创建时声明该变量的副本,线程退出时释放该变量。

而在局部作用域(如函数内),虽然变量的存储期等于线程的生命周期,但链接性和作用域仍然受局部变量规则约束。这意味着它在每个线程中只初始化一次,并且在多次函数调用中保持其值,类似于 static 局部变量,但每个线程都有自己独立的一份。

static_assert (C++11)

static_assert 编译时断言。

1
2
static_assert ( bool_constexpr , message )
static_assert ( bool_constexpr ) (C++17 起)

noexcept (C++11)

noexcept用于指定函数是否可能抛出异常。它取代了 C++03 中动态异常规范 (throw())。

1
void my_func() noexcept; // 表示此函数不会抛出异常

如果 noexcept函数抛出了异常,程序会调用 std::terminate()终止。

noexcept也可以作为一个操作符,在模板中检查表达式是否可能抛出异常:

1
2
template <typename T>
void func() noexcept(noexcept(T())) {} // 根据T()是否抛出异常来决定

alignas/alignof (C++11)

alignas用于指定变量或类型的对齐要求,而alignof用于获取类型的对齐要求。

1
2
alignas(16) int arr[4]; // arr 按 16 字节对齐
std::cout << alignof(int) << std::endl; // 输出 int 的对齐值

《Windows内存体系(4)--内存对齐》 中介绍了编译器的内存对齐规则,alignas允许你显式指定一个比编译器默认对齐要求更严格(更大)​​ 的对齐值。它并不能“减弱”或“改变”编译器固有的对齐规则,而是在此基础上增加新的约束。