自从C++11始可以指明属性(attributes),语法如下:
[[ attribute-list ]] (since C++11)
[[ using attribute-namespace : attribute-list ]] (since C++17)
其中:
(1).attribute-list是零个或多个属性的逗号分隔序列(可能以省略号...结尾表示包扩展);
(2).如果using namespace: 出现在属性列表的开头,则属性列表中的其他属性都不能指定命名空间:using中指定的命名空间适用于所有属性.
属性是现代C++的关键功能之一,它允许程序员向编译器指定附加信息(additional information),以强制执行约束(条件)、优化某些代码片段或执行某些特定代码生成。简而言之,属性充当编译器的注释(annotation),它提供有关代码的附加信息,用于优化目的并对其强制执行某些条件。
属性为实现定义的语言扩展提供统一的标准语法(Attributes provide the unified standard syntax for implementation-defined language extensions),例如GNU和IBM语言扩展__attribute__((...))、Microsoft扩展__declspec()等。
属性几乎可以在C++程序中的任何地方使用,并且可以应用于几乎所有内容:类型、变量、函数、to names、代码块、整个翻译单元,尽管每个特定属性仅在其所在位置有效。
在声明中(In declarations),属性可以出现在整个声明之前,也可以直接出现在所声明的实体名称(the name of the entity)之后,在这种情况下,它们会被组合起来(combined)。在大多数其他情况下,属性适用于前一个实体。
两个连续的左方括号标记([[)仅在引入属性说明符(attribute-specifier)或在属性参数内部时才可能出现。
Standard attributes:
(1).标准属性不能在语法上被忽略:它们不能包含语法错误,必须应用于正确的目标,并且参数中的实体必须为ODR-used。
(2).标准属性也不能在语义上被忽略:删除特定标准属性的所有实例后的行为对于具有该属性的原始程序来说是一致的行为。
C++11中的标准属性:
(1).[[noreturn]]:指示函数在完成后不会将控制流返回给调用函数(例如终止应用程序、引发异常、无限循环等的函数)
(2).[[carries_dependency]]:indicates that dependency chain in release-consume std::memory_order propagates in and out of the function
C++14中的标准属性:
(1).[[deprecated]]:表示使用此属性声明的名称或实体(name or entity)已被弃用,由于某种原因不鼓励使用。该属性可以应用于命名空间、函数、类结构或变量
(2).[[deprecated("reason")]]:给出被弃用的原因
C++17中的标准属性:
(1).[[fallthrough]]:表示switch语句中前一个case标签的失败是故意的(缺少break语句),不应该由警告失败的编译器来诊断。注:与其他属性不同,fallthrough在声明后需要一个分号,而且不能在switch语句的最后一个分支使用它
(2).[[nodiscard]]:鼓励编译器在返回值被丢弃时发出警告。如果一个函数返回一个值并被标记为nodiscard,那么返回值必须由调用者使用而不是被丢弃
(3).[[maybe_unused]]:抑制对未使用的实体发出警告,例如:未使用的变量或函数的未使用的参数
C++中属性的用途:
(1).对代码实施约束:如约束特定函数的参数必须满足才能执行的条件(前提条件)
(2).为优化目的向编译器提供附加信息
(3).抑制(suppress)在代码中出现的某些警告和错误:如[[maybe_unused]]和[[fallthrough]]之类的属性
标准属性与非标准属性之间的区别:
(1).标准属性由标准指定并存在于所有编译器中;而非标准属性由编译期供应商提供并非在所有编译器中都存在;
(2).标准属性代码完全可移植,没有任何问题或警告;而非标准属性自C++17起代码变得可移植,但编译器中仍然会出现一些警告或错误;
在C++11或14中,如果编译器无法识别某个属性,则会产生错误并阻止代码编译。大多数支持C++17的编译器现在都会忽略未定义的属性,并在遇到时生成警告。
自从C++17起:
(1).属性现在可以用来标记命名空间,如下,也可以应用于内联的和匿名的命名空间
namespace [[deprecated]] DraftAPI { ... }
(2).属性现在可以标记枚举类型的值
(3).用户自定义的属性一般应该定义在自定义的命名空间中。现在可以使用using前缀来避免为每一个属性重复输入命名空间,如:
[[MyLib::WebService, MyLib::RestService, MyLib::doc("html")]] void foo();
可以替换为:
[[using MyLib: WebService, RestService, doc("html")]] void foo();
注:在使用了using前缀时重复命名空间将导致错误
以下为测试代码:
namespace {
[[noreturn]] void fun1() { throw 1; }
[[deprecated("Susceptible to buffer overflow")]] void set_addr(char* addr) { std::cout << "addr:" << addr << "\n"; }
//[[deprecated]] void set_addr(char* addr) { std::cout << "addr:" << addr << "\n"; }
[[nodiscard]] int fun2() { return 0; }
// the attributes will work on their respective
[[msvc::deprecated]] [[gnu::deprecated]] void set_name(char* name) { } // warning C5030: 未识别属性"msvc::deprecated"
// warning C5030: 未识别属性"gnu::deprecated"
} // namespace
int test_attributes()
{
// standard attributes
// 1.noreturn: 指示函数在完成后不会将控制流返回给调用函数
try {
fun1();
} catch (int i) {
std::cout << "value:" << i << "\n";
}
// 2.deprecated: 表示使用此属性声明的名称或实体已被弃用,由于某种原因不鼓励使用
//set_addr("https://blog.csdn.net/fengbingchun/"); // error C4996: 'anonymous-namespace'::set_addr': Susceptible to buffer overflow
// 3.nodiscard: 鼓励编译器在返回值被丢弃时发出警告
fun2(); // warning C4834: 放弃具有 "nodiscard" 属性的函数的返回值
// 4.maybe_unused: 抑制对未使用的实体发出警告
[[maybe_unused]] int var{ 0 }; //
// 5.fallthrough: 表示switch语句中前一个case标签的失败是故意的(缺少break语句),不应该由警告失败的编译器来诊断
int place{ 1 };
switch (place) {
case 1:
std::cout << "very ";
[[fallthrough]];
case 2:
std::cout << "well\n";
break;
default:
std::cout << "OK\n";
break;
}
// 在C++11或14中,如果编译器无法识别某个属性,则会产生错误并阻止代码编译.大多数支持C++17的编译器现在都会忽略未定义的属性,并在遇到时生成警告
[[zzzzz]] auto str{ "beijing" }; // warning C5030: 未识别属性"zzzzz"
return 0;
}
执行结果如下图所示: