C++ 中的特性

C++ 标准定义了一组通用属性。 它还允许编译器供应商在其特定的命名空间中定义自己的属性。 但是,编译器只需识别标准中定义的属性。

在某些情况下,标准属性与编译器特定的 __declspec 参数重叠。 在 Microsoft C++ 中,可以使用 [[deprecated]] 属性而不使用 __declspec(deprecated)[[deprecated]] 属性可由任何符合标准的编译器识别。 对于所有其他 __declspec 参数(例如 dllimportdllexport),到目前为止还没有等效的属性,因此必须继续使用 __declspec 语法。 属性不影响类型系统,也不会改变程序的含义。 编译器会忽略它们无法识别的属性值。

Visual Studio 2017 版本 15.3 和更高版本(适用于 /std:c++17 和更高版本):在属性列表的范围内,可以使用单个 using 引入器为所有名称指定命名空间

void g() {
    [[using rpr: kernel, target(cpu,gpu)]] // equivalent to [[ rpr::kernel, rpr::target(cpu,gpu) ]]
    do task();
}

C++ 标准属性

在 C++11 中,属性提供一种使用附加信息来批注 C++ 构造(包括但不限于类、函数、变量和块)的标准化方式。 属性不一定特定于供应商。 编译器可以使用此信息来生成信息性消息,或者在编译属性代码时应用特殊的逻辑。 编译器会忽略它无法识别的任何属性,这意味着你无法使用此语法来定义自己的自定义属性。 属性括在双方括号中:

[[deprecated]]
void Foo(int);

属性代表 #pragma 指令、__declspec() (Visual C++) 或 __attribute__ (GNU) 等供应商特定扩展的标准化替代项。 但是,在大多数情况下,你仍需使用供应商特定的构造。 标准目前指定了符合标准的编译器应识别的以下属性。

[[carries_dependency]]

[[carries_dependency]] 属性指定函数传播线程同步的数据依赖项顺序。 可将该属性应用于一个或多个参数,以指定传入的参数要将依赖项带入函数主体中。 可将该属性应用于函数本身,以指定返回值要将依赖项带出函数。 编译器可以使用此信息来生成更有效的代码。

[[deprecated]]

Visual Studio 2015 及更高版本:[[deprecated]] 属性指定函数不适合使用。 或者,它可能不存在于库接口的将来版本中。 [[deprecated]] 属性可应用于类、typedef 名称、变量、非静态数据成员、函数、命名空间、枚举、枚举器或模板专用化的声明。 当客户端代码尝试调用该函数时,编译器可以使用此属性来生成信息性消息。 当 Microsoft C++ 编译器检测到使用 [[deprecated]] 项时,会发出编译器警告 C4996

[[fallthrough]]

Visual Studio 2017 和更高版本:(适用于 /std:c++17 和更高版本。)[[fallthrough]] 属性可在 switch 语句的上下文中用作向编译器(或阅读代码的任何人)显示的提示,指出失败行为是有意的。 Microsoft C++ 编译器当前不会对回退行为发出警告,因此此属性对编译器行为没有影响。

[[likely]]

Visual Studio 2019 版本 16.6 及更高版本:(适用于 /std:c++20 及更高版本。)[[likely]] 属性向编译器指定提示,指出属性化标签或语句的代码路径的执行可能性高于替代项。 在 Microsoft 编译器中,[[likely]] 属性将块标记为“热代码”,这会增加内部优化分数。 针对速度进行优化时,分数增加得更多;针对大小进行优化时,增加的幅度不太大。 净分数会影响内联、循环展开和向量化优化的可能性。 [[likely]][[unlikely]] 的效果类似于按配置优化,但仅限于当前转换单元的范围。 该属性尚未针对此属性实施块重新排序优化。

[[maybe_unused]]

Visual Studio 2017 版本 15.3 及更高版本:(适用于 /std:c++17 及更高版本。)[[maybe_unused]] 属性指定变量、函数、类、typedef、非静态数据成员、枚举或模板专用化可能是有意不使用的。 当未使用标记为 [[maybe_unused]] 的实体时,编译器不会发出警告。 未使用属性声明的实体以后可以使用属性来重新声明,反之亦然。 分析某个实体的首个标记为 [[maybe_unused]] 的声明后,该实体被视为已标记,适用于当前转换单元的其余部分

[[nodiscard]]

Visual Studio 2017 版本 15.3 和更高版本:(适用于 /std:c++17 和更高版本。)指定不应该丢弃函数的返回值。 引发警告 C4834,如以下示例中所示:

[[nodiscard]]
int foo(int i) { return i * i; }

int main()
{
    foo(42); //warning C4834: discarding return value of function with 'nodiscard' attribute
    return 0;
}

[[noreturn]]

[[noreturn]] 属性指定函数永不返回;换言之,它始终引发异常或退出。 编译器可以针对 [[noreturn]] 实体调整其编译规则。

[[unlikely]]

Visual Studio 2019 版本 16.6 及更高版本:(适用于 /std:c++20 及更高版本。)[[unlikely]] 属性向编译器指定提示,指出属性化标签或语句的代码路径的执行可能性低于替代项。 在 Microsoft 编译器中,[[unlikely]] 属性将块标记为“冷代码”,这会减少内部优化分数。 针对大小进行优化时,分数减少得更多;针对速度进行优化时,减少的幅度不太大。 净分数会影响内联、循环展开和向量化优化的可能性。 该属性尚未针对此属性实施块重新排序优化。

Microsoft 特定的属性

[[gsl::suppress(rules)]]

Microsoft 特定的 [[gsl::suppress(rules)]] 属性用于抑制在代码中强制实施准则支持库 (GSL) 规则的检查器发出的警告。 例如,考虑以下代码片段:

int main()
{
    int arr[10]; // GSL warning C26494 will be fired
    int* p = arr; // GSL warning C26485 will be fired
    [[gsl::suppress(bounds.1)]] // This attribute suppresses Bounds rule #1
    {
        int* q = p + 1; // GSL warning C26481 suppressed
        p = q--; // GSL warning C26481 suppressed
    }
}

该示例引发了以下警告:

  • C26494(类型规则 5:始终初始化对象。)

  • C26485(边界规则 3:没有指针衰减的数组。)

  • C26481(边界规则 1:不要使用指针算术。请改用范围。)

使用安装并激活的 CppCoreCheck 代码分析工具编译此代码时,会触发前两个警告。 但由于使用了该属性,第三个警告不会触发。 可以通过写入 [[gsl::suppress(bounds)]] 而不包括特定规则编号来抑制整个边界配置文件。 C++ Core Guidelines 可帮助你编写更好、更安全的代码。 使用 suppress 属性可以轻松关闭不需要的警告。

[[msvc::flatten]]

Microsoft 特定属性 [[msvc::flatten]][[msvc::forceinline_calls]] 非常相似,并且可以在相同位置以相同方式使用。 不同之处在于,[[msvc::flatten]] 会递归地 [[msvc::forceinline_calls]] 其应用范围内的所有调用,直到没有调用为止。 这可能会对函数的代码大小增长或编译器的吞吐量产生影响,必须手动管理。

[[msvc::forceinline]]

当放置在函数声明之前时,Microsoft 特定属性 [[msvc::forceinline]]__forceinline 具有相同的含义。

[[msvc::forceinline_calls]]

Microsoft 特定属性 [[msvc::forceinline_calls]] 可以放置在语句或块之上或之前。 它会导致内联启发式尝试 [[msvc::forceinline]] 该语句或块中的所有调用:

void f() {
    [[msvc::forceinline_calls]]
    {
        foo();
        bar();
    }
    ...
    [[msvc::forceinline_calls]]
    bar();
    
    foo();
}

foo 的第一次调用以及对 bar 的两次调用都被视为声明为 __forceinline。 对 foo 的第二次调用不会被视为 __forceinline

[[msvc::intrinsic]]

[[msvc::intrinsic]] 属性对其应用到的函数有三个约束:

  • 该函数不能具有递归性;它的主体必须只有一个 return 语句,该语句带有从参数类型到返回类型的 static_cast
  • 该函数只能接受单个参数。
  • /permissive- 编译器选项是必需的。 (默认情况下,/std:c++20 及更高版本的选项意味着 /permissive-。)

Microsoft 特定的 [[msvc::intrinsic]] 属性告知编译器内联一个元函数,该元函数充当从参数类型到返回类型的命名转换。 当该属性出现在函数定义中时,编译器会用简单的强制转换替换对该函数的所有调用。 [[msvc::intrinsic]] 属性在 Visual Studio 2022 版本 17.5 预览版 2 及更高版本中可用。 此属性仅适用于其后面的特定函数。

示例

在此示例代码中,应用于 my_move 函数的 [[msvc::intrinsic]] 属性使编译器将该函数的调用替换为其主体中的内联静态强制转换:

template <typename T>
[[msvc::intrinsic]] T&& my_move(T&& t) { return static_cast<T&&>(t); }

void f() {
    int i = 0;
    i = my_move(i);
}

[[msvc::noinline]]

当放置在函数声明之前时,Microsoft 特定属性 [[msvc::noinline]]declspec(noinline) 具有相同的含义。

[[msvc::noinline_calls]]

Microsoft 特定属性 [[msvc::noinline_calls]] 的用法与 [[msvc::forceinline_calls]] 相同。 它可以放置在任何语句或块之前。 它不是强制内联该块中的所有调用,而是对应用到的范围禁用内联。

[[msvc::no_tls_guard]]

Microsoft 特定的 [[msvc::no_tls_guard]] 属性禁止在首次访问 DLL 中的线程局部变量时检查初始化。 默认情况下,在使用 Visual Studio 2019 版本 16.5 及更高版本构建的代码中启用这些检查。 此属性仅适用于其后面的特定变量。 若要全局禁用检查,请使用 /Zc:tlsGuards- 编译器选项。