添加链接
link之家
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接
首发于 C++基础

C++中的标准属性(Attribute)说明符

自C++11起,C++就正式引入了属性说明符,它允许程序员给编译器提供额外的信息让其对程序进行优化、检查、约束。它并不是新东西,各家编译器本来就有自带的各种属性,标准属性把一些经典的属性给标准化了。我们这里来聊聊C++中的标准属性。

C++中的标准属性

编译警告相关的属性

这类属性和编译期警告有关,可以通过它们打开和关闭某些警告,它不会影响最终编译出的程序。这类属性也是最为常用的且不容易出错的属性。

[[deprecated]]

指示声明有此属性的名字或实体被弃用,即 允许 但因故 不鼓励 使用。实体包括类型(struct,class,union)、别名、变量、非静态数据成员、函数、命名空间,枚举类型、枚举类型中的一项、模版特化这些实体。

deprecated语法

使用案例:

[[deprecated("Please use int foo2()")]]int foo() { return 2;}

如果调用 foo 函数,编译器就会报警告:

<source>:9:15: warning: 'int foo()' is deprecated: Please use int foo2() [-Wdeprecated-declarations]
    9 |     return foo();
      |            ~~~^~

[[maybe_unused]]

抑制对未使用实体的警告,实体包括类型(struct,class,union)、别名、变量、非静态数据成员、函数、命名空间,枚举类型、枚举类型、结构化绑定。

一种典型的场景是关闭一些因为log级别变化而产生未使用变量编译警告:

#ifdef ENABLE_DEBUG_LOG
#define LOG_DEBUG(x)  {  std::cout <<"D: " << x <<"\n";}
#else
#define LOG_DEBUG(x) // nothing
#endif
int foo() {
    [[maybe_unused]]const char* errorMessage="Wrong";
    // 生产环境中debug级别的日志不会被打印,所以errorMessage实际上是不会被使用的
    LOG_DEBUG(errorMessage)
    return 0;

[[fallthrough]]

指示 switch 语句中从前一标号直落是有意的,而在发生直落时给出警告的编译器不应诊断它。

void f(int n)
    int local{0};
    switch (n)
        case 1:
        case 2:
            local += 1;
            [[fallthrough]];
        case 3: // 直落时不警告
            local *= 2;
        case 4:
            while (false)
                [[fallthrough]]; // 非良构:下一语句不是同一迭代的一部分
        case 6:
            [[fallthrough]]; // 非良构:没有后继的 case 或 default 标号

[[nodiscard]]

可被用于函数声明、类声明、枚举类型声明中,若从并非转型到 void 的弃值表达式中,则鼓励编译器发布警告。

调用声明为 nodiscard 的函数,或
调用按值返回声明为 nodiscard 的枚举或类的函数,或
以显式类型转换或 static_cast 形式调用声明为 nodiscard 的构造函数,或
以显式类型转换或 static_cast 形式构造声明为 nodiscard 的枚举或类的对象,

它有好几个使用场景:

一个场景是迫使程序员检查错误码,如下:

enum class[[nodiscard]] ErrorCode {
    Success,
    Wrong
// 当调用该函数却没有检查其返回值时,会报编译警告
ErrorCode foo() {
    return ErrorCode::Success;

一个使用场景是警告资源泄露,比如程序员分配了内存却不使用它,这会造成资源泄露。因此 operator new nodiscard 的属性。

另一个使用场景是警告调用错误,比如在对C++容器不熟悉的程序员很容易将容器的成员函数 empty() 认为是清空容器,因此几乎所有容器的 empty() 成员函数都有 nodiscard 属性,一旦发生调用该函数却不检查返回值,大概率是 empty() 被错误使用了。

可能触发编译优化的属性

这类属性 可能 触发编译优化,这些优化会影响最终编译出来的程序。这些属性可能会引发程序错误,谨慎使用。

[[noreturn]]

指示函数不会返回,直接终止程序。编译器会根据这个属性对程序进行优化,比如会直接忽略在该函数之后的代码就。如果该函数返回,那么程序行为未定义。

[[noreturn]]示例
以上标准库函数有[[noreturn]]属性

[[likely]]、[[unlikely]]

允许编译器为包含该语句的执行路径,比任何其他不包含该语句的执行路径,更可能或更不可能的情况进行优化。它既可以用于 if 语句也可以用于 switch 语句。编译器可能会根据该属性更改代码布局,比如对instruction cache更加友好。它不会影响芯片的分支预测功能,貌似没有指令可以指导分支预测电路。

如下用泰勒展开计算 cos 的代码,用了该种属性可以提速一倍:

namespace with_attributes
    constexpr double pow(double x, long long n) noexcept
        if (n > 0) [[likely]]
            return x * pow(x, n - 1);
        else [[unlikely]]
            return 1;
    constexpr long long fact(long long n) noexcept
        if (n > 1) [[likely]]
            return n * fact(n - 1);
        else [[unlikely]]
            return 1;
    constexpr double cos(double x) noexcept
        constexpr long long precision{16LL};
        double y{};
        for (auto n{0LL}; n < precision; n += 2LL) [[likely]]
            y += pow(x, n) / (n & 2LL ? -fact(n) : fact(n));
        return y;
} // namespace with_attributes
namespace no_attributes
    constexpr double pow(double x, long long n) noexcept
        if (n > 0)
            return x * pow(x, n - 1);
            return 1;
    constexpr long long fact(long long n) noexcept
        if (n > 1)
            return n * fact(n - 1);
            return 1;
    constexpr double cos(double x) noexcept
        constexpr long long precision{16LL};
        double y{};
        for (auto n{0LL}; n < precision; n += 2LL)
            y += pow(x, n) / (n & 2LL ? -fact(n) : fact(n));
        return y;
} // namespace no_attributes
double gen_random() noexcept
    static std::random_device rd;
    static std::mt19937 gen(rd());
    static std::uniform_real_distribution<double> dis(-1.0, 1.0);
    return dis(gen);
volatile double sink{}; // ensures a side effect
int main()
    auto benchmark = [](auto fun, auto rem)
        const auto start = std::chrono::high_resolution_clock::now();
        for (auto size{1ULL}; size != 10'000'00ULL; ++size)
            sink = fun(gen_random());
        const std::chrono::duration<double> diff =
            std::chrono::high_resolution_clock::now() - start;
        std::cout << "Time: " << std::fixed << std::setprecision(6) << diff.count()
                  << " sec " << rem << std::endl; 
    benchmark(with_attributes::cos, "(with attributes)");
    benchmark(no_attributes::cos, "(without attributes)");
// 输出
// Time: 0.527918 sec (with attributes)
// Time: 0.984955 sec (without attributes)

[[assume]]

指示表达式在给定的位置永远会求值为 true ,编译器会根据该属性进行编译优化。因为假设在不成立时会导致未定义行为,所以不能经常使用它们。

有[[assume]]属性之后,汇编代码就不会考虑i小于零的情况

[[carries_dependency]]

用来传递 std::memory_order 中release-consume的依赖链进入函数,这允许编译器跳过不必要的内存栅栏指令。不推荐使用,甚至有专门的 P0371R1: Temporarily discourage memory_order_consume 用来传递 std::memory_order 中release-consume的依赖链进入函数,这允许编译器跳过不必要的内存栅栏指令。不推荐使用 paper

[[no_unique_address]]

允许此数据成员与其类的其他非静态数据成员或基类子对象重叠。可以用来优化空类成员变量,编译器可将它优化为不占空间,就像空基类优化一样。

struct Empty {};
struct EmptyBaseOptimization: public Empty {
    int i;
struct Foo {
    Empty empty;
    int i;
struct Foo2 {
    [[no_unique_address]] Empty empty;
    int i;
int main() {
    // 空类也是有大小的
    static_assert(sizeof(Empty) == 1);
    // 空基类优化会使得空基类不占用空间
    static_assert(sizeof(EmptyBaseOptimization) == 4);