C/C++ 不常用关键字/说明符汇总

本文提及的内容并不都是关键字。关键字不能作为符号名称使用,但部分说明符可以。例如,override 是 C++ 的说明符,用于显式地标记函数重载,但与关键字不同之处在于 override 可以作为符号名称使用:

int override = 100;

final

final 说明符将类的虚成员函数标记为不可重载,或将一个类标记为不可派生。

class Base {
public:
    virtual void foo() final;
};

class Derived {
public:
    virtual void foo() override; // Declaration of foo overrides a final function
};
class Base final {

};

class Derived : public Base { // Base is marked final

};

mutable

mutable 说明符允许在 const 成员函数中修改成员变量。

class Foo {
    mutable int value;
public:
    void setValue(int v) const {
        value = v; // ok
    }
};

struct Bar {
    mutable int count;
};

const Bar b{0};
b.count++; // ok

restrict

restrict 关键字是 C99 引入的一个类型限定符,表示指针所指向的数据只能通过该指针访问。这有助于编译器进行优化,因为它可以假设没有其他指针会修改该数据。需要指出的是,C++ 中没有 restrict 关键字,但在主流编译器平台均提供了相应的扩展,如 MSVC 中的 __restrict 和 GCC/Clang 中的 __restrict__

在这个意义上,可以说 C++ 在语法上并不是 C 的超集。

memcpy 函数是一个典型的使用 restrict 关键字的函数。该函数要求 srcdest 指针所指向的内存区域不能重叠。

void memcpy(void* restrict dest, void const* restrict src, std::size_t n);

memcpy 相对的是 memmove 函数,该函数允许 srcdest 指针所指向的内存区域重叠。memmove 在执行复制操作时,需要额外的逻辑处理重叠情况,因此开销略高于 memcpy

void memmove(void* dest, void const* src, std::size_t n);

alignas

alignas 关键字符用于指定类型或变量的对齐要求。它可以用于确保某个类型或变量按照特定的字节边界进行对齐,从而提高访问效率。例如,在 x86 体系结构下,cache line 的大小通常为 64 字节,因此对于频繁访问的数据结构,使用 alignas(64) 可以确保该数据结构在内存中按照 64 字节对齐,从而减少访问时的缓存行分割和伪共享问题。

alignas(64) int64_t some_data;

小于 64 字节的数据在非对齐的情况下被缓存时,可能被分散存放在两个不同的 cache line 中,导致访问时需要额外的内存访问,从而增加访问延迟。受到缓存一致性协议影响,访问非对齐的缓存数据会造成两个缓存行同时被锁定,占用更多时钟周期。

对分散在两个 cache line 中的数据进行操作时,会引发伪共享(false sharing)问题,从而降低多线程程序的性能。所谓伪共享,是指多个线程访问同一 cache line 中的不同数据时,导致频繁的缓存行无效化和重新加载,从而降低性能。

alignas 还可以用于定义具有特定对齐要求的类型:

struct alignas(16) AlignedStruct {
    int64_t a;
    int64_t b;
};

alignof

alignas 相对应的,alignof 关键字用于查询类型的对齐要求。它返回一个 std::size_t 类型的值,表示该类型的对齐要求(以字节为单位)。例如:

alignas(64) struct AlignedStruct {
    int64_t a;
    int64_t b;
};
std::cout << alignof(int) << std::endl; // 输出: 4
std::cout << alignof(AlignedStruct) << std::endl; // 输出: 64