nullptr (C++11)
nullptr 专门用于表示空指针,旨在解决传统 NULL 或 0 在类型安全性和代码清晰度上的缺陷。
nullptr 的类型为 std::nullptr_t,只能隐式转换为指针类型(包括原生指针、智能指针、托管句柄等),不能转换为整数类型,避免了与整型 0 的混淆。
而传统 NULL 是宏(通常定义为 0 或 (void*)0),可能被误解释为整数,导致类型错误。
在函数重载场景中,nullptr 可明确选择指针版本的重载函数,避免因 NULL 被解释为整数导致的错误调用:
1 | void foo(int); // 整型版本 |
而且在模板中可精确匹配指针类型,避免推导为整数:
1 |
|
override/final (C++11)
override 是一个用于显式标记派生类中重写(Override)基类虚函数的关键字。它通过强制编译器检查和增强代码可读性的方式,显著提升了继承体系的安全性和可维护性。
override 明确告知编译器和开发者:当前函数旨在重写基类的虚函数,若派生类函数签名(参数类型、const 限定、引用限定等)与基类虚函数不完全匹配,编译器会立即报错,避免隐蔽的错误。
final 关键字提供两种限制能力:
禁止类被继承
标记为 final 的类不可作为基类,编译时会阻止派生类继承它。
1
2class Base final { /* ... */ }; // Base 类不可被继承
class Derived : public Base { }; // ❌ 编译错误:Base 是 final 的禁止虚函数被重写
标记为 final 的虚函数在派生类中不可被覆盖,确保基类设计意图不被破坏。
1
2
3
4
5
6
7
8class Base {
public:
virtual void func() final; // func 不可被重写
};
class Derived : public Base {
public:
void func() override; // ❌ 编译错误:func 是 final 的
};
final 常与 override 配合使用:
- override:确保派生类函数正确重写基类虚函数(签名不匹配时报错)。
- final:在派生类中进一步禁止后续重写。
1 | class Derived : public Base { |
constexpr (C++11)
constexpr 用于在编译时计算常量表达式,提升程序性能和安全性。其核心机制是将计算从运行时转移到编译时,减少运行时开销并支持编译期逻辑验证。
下面是一个在编译器计算斐波那契数列的例子:
1 | constexpr int factorial(int n) { |
众所周知,在声明数组时,数组大小需要为常量,使用 constexpr 可以通过下面方式声明数组:
1 | constexpr int getArraySize(int type) { |
在使用 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
17constexpr 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 | auto x = 42; // 推导为 int |
decltype (C++11)
decltype用于查询表达式的类型,发生在编译期,且不会对表达式进行求值。
1 | int i = 4; |
decltype在泛型编程中常用于追踪函数返回类型:
1 | template <typename T, typename 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 | static_assert ( bool_constexpr , message ) |
noexcept (C++11)
noexcept用于指定函数是否可能抛出异常。它取代了 C++03 中动态异常规范 (throw())。
1 | void my_func() noexcept; // 表示此函数不会抛出异常 |
如果 noexcept函数抛出了异常,程序会调用 std::terminate()终止。
noexcept也可以作为一个操作符,在模板中检查表达式是否可能抛出异常:
1 | template <typename T> |
alignas/alignof (C++11)
alignas用于指定变量或类型的对齐要求,而alignof用于获取类型的对齐要求。
1 | alignas(16) int arr[4]; // arr 按 16 字节对齐 |
在 《Windows内存体系(4)--内存对齐》 中介绍了编译器的内存对齐规则,alignas允许你显式指定一个比编译器默认对齐要求更严格(更大) 的对齐值。它并不能“减弱”或“改变”编译器固有的对齐规则,而是在此基础上增加新的约束。