属性是现代C++的关键功能之一,它允许程序员为编译器指定附加信息,以强制执行约束(条件),优化某些代码段或执行某些特定的代码生成。简而言之,属性充当编译器的注释或注释,编译器提供有关代码的其他信息以用于优化目的并在其上强制执行某些条件。它们是C++ 11中引入的,至今仍是C++的最佳功能之一,并且随着C++的每个新版本不断发展。
句法:
// C++11
[[attribute-list]]
// C++17
[[using attribute-namespace:attribute-list]]
// Upcoming C++20
[[contract-attribute-token contract-level identifier : expression]]
Except for some specific ones, most of the attributes
can be applied with variables, functions, classes,
structures etc.
C++中属性的用途
- 要对代码施加约束:
这里的约束是指一个条件,特定函数的参数必须满足该条件才能执行(前提条件)。在以前的C++版本中,用于指定约束的代码是以这种方式编写的
int f(int i) { if (i > 0) return i; else return -1; // Code }
它提高了代码的可读性,并避免了为检查参数而写在函数内部的混乱情况。
int f(int i)[[expects:i > 0]] { // Code }
- 出于优化目的,向编译器提供其他信息:
编译器在优化方面非常擅长,但与人类相比,它们仍然滞后于某些地方,并提出了效率不高的通用代码。这主要是由于缺乏有关人类“问题”的其他信息而发生的。为了最大程度地减少此问题,C++标准引入了一些新属性,这些属性允许为编译器而不是代码语句本身指定更多内容。这样的例子曾经是可能的。
int f(int i) { switch (i) { case 1: [[fallthrough]]; [[likely]] case 2 : return 1; } return -1; }
当该语句之后是可能的编译器时,会对该语句进行特殊优化,从而提高代码的整体性能。此类属性的一些示例是[carries_dependency],[可能],[不太可能]
- 禁止程序员打算在其代码中使用的某些警告和错误:
这种情况很少发生,但是有时程序员有意尝试编写一个错误代码,该错误代码会被编译器检测到,并报告为错误或警告。一个这样的例子就是未使用的变量(由于某种特定原因而留在该状态)或switch语句(在某些情况下,break语句在某些情况下不会引起下降条件)放置在switch语句中。为了避免在这种情况下出现错误和警告,C++提供了[maybe_unused]和[ fallthrough]之类的属性,可防止编译器生成警告或错误。
#include
#include int main() { // Set debug mode in compiler or 'R' [[maybe_unused]] char mg_brk = 'D'; // Compiler does not emit any warnings // or error on this unused variable }
C++中的标准属性列表
- C++ 11
- noreturn:表示该函数不返回值
用法:
[[noreturn]] void f();
虽然看一下上面的代码中,出现的问题是什么是具有不返回的时候返回类型实际上是无效的呢?如果函数的类型为void,则实际上它会返回没有值的调用方,但如果情况是函数从不返回给调用方(例如无限循环),则添加noreturn属性会向编译器提供提示优化代码或生成更好的警告。
例子:
#include
#include [[noreturn]] void f() { // Some code that does not return // back the control to the caller // In this case the function returns // back to the caller without a value // This is the reason why the // warning "noreturn' function does return' arises } void g() { std::cout << "Code is intented to reach here"; } int main() { f(); g(); } 警告:
main.cpp: In function 'void f()': main.cpp:8:1: warning: 'noreturn' function does return } ^
C++ 14
- 不推荐使用:指示使用此属性声明的名称或实体已过时,并且出于某些特定原因不得使用。此属性可以应用于名称空间,函数,类结构或变量。
用法:
[[deprecated("Reason for deprecation")]] // For Class/Struct/Union struct [[deprecated]] S; // For Functions [[deprecated]] void f(); // For namespaces namespace [[deprecated]] ns{} // For variables (including static data members) [[deprecated]] int x;
例子:
#include
#include [[deprecated("Susceptible to buffer overflow")]] void gets(char* str) { // Code for gets dummy // (Although original function has // char* as return type) } void gets_n(std::string& str) { // Dummy code char st[100]; std::cout << "Successfully Executed"; std::cin.getline(st, 100); str = std::string(st); // Code for new gets } int main() { char a[100]; gets(a); // std::string str; // gets_n(str); } 警告:
main.cpp: In function 'int main()': main.cpp:26:9: warning: 'void gets(char*)' is deprecated: Susceptible to buffer overflow [-Wdeprecated-declarations] gets(a); ^
C++ 17
- nodiscard:用nodiscard声明的实体的调用者不应忽略其返回值。简单地说,如果一个函数返回一个值并将其标记为nodiscard,则返回值必须由调用方使用,而不是被丢弃。
用法 :
// Functions [[nodiscard]] void f(); // Class/Struct declaration struct [[nodiscard]] my_struct{};
带有函数的nodiscard和带有struct / class声明的nodiscard之间的主要区别在于,在函数的情况下,nodiscard仅适用于被声明为不丢弃的特定函数,而在class / struct声明的情况下,nodiscard应用于返回的每个单个函数。按值标记的未丢弃对象。
例子:
#include
#include // Return value must be utilized by the caller [[nodiscard]] int f() { return 0; } class[[nodiscard]] my_class{}; // Automatically becomes nodiscard marked my_class fun() { return my_class(); } int main() { int x{ 1 }; // No error as value is utilised // x= f(); // Error : Value is not utilised f(); // Value not utilised error // fun() ; return x; } 警告:
prog.cpp:5:21: warning: 'nodiscard' attribute directive ignored [-Wattributes] [[nodiscard]] int f() ^ prog.cpp:10:20: warning: 'nodiscard' attribute directive ignored [-Wattributes] class[[nodiscard]] my_class{}; ^
- may_unused :用于禁止对任何未使用的实体发出警告(例如:未使用的变量或函数的未使用参数)。
用法:
//Variables [[maybe_used]] bool log_var = true; //Functions [[maybe_unused]] void log_without_warning(); //Function arguments void f([[maybe_unused]] int a, int b);
例子:
#include
#include int main() { // Set debug mode in compiler or 'R' [[maybe_unused]] char mg_brk = 'D'; // Compiler does not emit any warnings // or error on this unused variable } - 失败:
[[fallthrough]]表示switch语句中的失败是故意的。在switch语句中缺少中断或返回通常被认为是程序员的错误,但是在某些情况下,失败可能会导致某些非常简洁的代码,因此被使用。注意:与其他属性不同,fallthrough在声明后需要使用分号。
例子:
void process_alert(Alert alert) { switch (alert) { case Alert::Red: evacuate(); // Compiler emits a warning here // thinking it is done by mistake case Alert::Orange: trigger_alarm(); // this attribute needs semicolon [[fallthrough]]; // Warning suppressed by [[fallthrough]] case Alert::Yellow: record_alert(); return; case Alert::Green: return; } }
即将到来的C++ 20属性
- 可能:用于优化某些语句的执行可能性要高于其他语句。现在,可能已在最新版本的GCC编译器中提供了用于实验目的的信息。
例子
int f(int i) { switch (i) { case 1: [[fallthrough]]; [[likely]] case 2 : return 1; } return 2; }
- no_unique_address :指示此数据成员不必具有与该类的所有其他非静态数据成员不同的地址。这意味着,如果类由空类型组成,则编译器可以对其执行空基础优化。
例子:
// empty class ( No state!) struct Empty { }; struct X { int i; Empty e; }; struct Y { int i; [[no_unique_address]] Empty e; }; int main() { // the size of any object of // empty class type is at least 1 static_assert(sizeof(Empty) >= 1); // at least one more byte is needed // to give e a unique address static_assert(sizeof(X) >= sizeof(int) + 1); // empty base optimization applied static_assert(sizeof(Y) == sizeof(int)); }
- 期望:它指定参数要执行的特定函数必须满足的条件(以合同的形式)。
用法:
return_type func ( args...) [[expects : precondition]]
例子:
void list(node* n)[[expects:n != nullptr]]
违反合同将导致违反处理程序的调用,或者如果未指定,则调用std :: terminate()
标准和非标准属性之间的区别
Standard Attributes | Non Standard Attributes |
---|---|
Specified by the standard and Are present In all the compilers |
Provided by the compiler vendors And are not present across all compilers |
Code is completely portable Without any warnings or issues |
Although code becomes portable (since C++17) for non-standard attributes in “standard syntax” some warnings/errors still creep in the compilers. |
Attributes are written inside the Standard syntax [[atr]] |
Some of the attributes are written inside the non Standard syntax and others are written within a compiler specific keyword like declspec() or __attribute__ |
Standard attributes are not present In any enclosing namespace |
Nonstandard attributes are written in standard syntax With their enclosing namespace [[namespace::attr]] |
自C++ 11以来的变化
- 忽略未知属性:
从C++ 17开始,C++中针对属性功能引入的主要更改之一是关于编译器对未知属性的澄清。在C++ 11或14中,如果编译器无法识别属性,则它将产生错误并阻止代码被编译。作为一种解决方法,程序员必须从代码中删除该属性以使其起作用。这为可移植性引入了一个主要问题。除了标准属性外,不能使用任何特定于供应商的属性,因为代码会中断。这阻止了此功能的实际使用。
作为解决方案,该标准强制所有编译器忽略其未定义的属性。这使程序员可以在其代码中自由使用特定于供应商的属性,并确保代码仍可移植。现在,大多数支持C++ 17的编译器都会忽略未定义的属性,并在遇到该警告时发出警告。这使程序员可以像现在一样使代码更加灵活,他们可以在不同供应商的名称空间下为同一操作指定多个属性。 (支持:MSVC(NOT YET),GCC,CLANG(YES))
例子:
// Here the attributes will work on their respective [[msvc::deprecated]][[gnu::deprecated]] char* gets(char* str) compilers
- 使用属性名称空间而无需重复:
在C++ 17中,放宽了一些有关“非标准”属性使用的规则。一种这样的情况是使用后续的非标准属性为名称空间添加前缀。在C++ 11或14中,当多个属性一起编写时,每个属性都必须以其封闭的名称空间作为前缀,这引起了如下所示的代码模式。
[[ gnu::always_inline, gnu::const, gnu::hot, nodiscard ]] int f();
查看上面的代码,可以看到它看起来很肿和混乱。因此,委员会决定一起“简化使用多个属性时的案例”。到目前为止,对于程序员来说,并不需要一次又一次地将名称空间与后续属性一起使用作为前缀。这引起了下面显示的代码模式,看起来很干净而且很容易理解。
[[using gnu:const, always_inline]] int f() { return 0; }
- 特定代码段上的多个属性:
现在可以将几个属性应用于C++中的某些代码。在这种情况下,编译器将按照每个属性的写入顺序对其进行评估。这使程序员可以编写可包含多个约束的代码段。
例子:
#include
// Not implemented by compilers as of now // but will be implemented in the future [[nodiscard]] int f(int i)[[expects:i > 0]] { std::cout << " Always greater than 0!" << " and return val must " << "always be utilized"; }
C++和C#中的属性之间的区别
C#和C++中的属性之间存在显着差异。对于C#,程序员可以通过从System.Attribute派生而定义新的属性。而在C++中,元信息由编译器固定,不能用于定义新的用户定义属性。设置此限制是为了防止该语言演变为可能使该语言变得更加复杂的新形式。