C++的属性指示符,有点类似Java中Anotation, 是可以实现定义的。它主要是用来引入一些对象,类型,代码的属性,也可以理解为限制对象,类型,代码的一些行为。它为实现定义的语言扩展提供标准统一的语法,比如GNU和IBM的__attribute__((…))
,微软的__declspec()
语言扩展,等等。
尽管每个特殊的属性仅当实现允许时才有效,但是属性几乎能在C++程序的任何地方使用,同时也可以被应用到,类型,变量,函数,命名,代码块,以及整个的翻译单元,几乎所有的实体上。比如,[[likely]]
属性只能用在if
语句上边,不能用在函数声明上;[[omp::parallel()]] 属性只能用在for
循环语句上,而不能用在类型上面。(这两个属性只是为了举例方便而虚构的,实际并不存在)
在声明时,属性可能出现在实体声明前后,结合使用的。大多数情况,属性声明都放在实体声明前面,尽管对齐指示符
的语法有些不同,但是它是属性指示符
的一部分。它可能出现在[[]]
属性的地方,也可能和他们混合使用(但是只能用在允许对齐的地方) 两个连续的左中括号[[
,只能当引入属性指示符时或者在一个属性的参数中出现
标准的属性指示符的格式为
[[identifier]] (since c++11)
[[ using attribute-namespace::identifier]] (since c++17)
[[identifier(argument-list)]] (since c++11)
[[ using attribute-namespace:identifier(argument-list)]] (since c++17)
注意:
如果 命令空间出现在属性类表的开头,那么属性列表中的其他属性不能指定一个命名空间,该属性
类列表中的所有属性共享一个命名空间。
标准的属性指示符
属性指示符名称 | 用途 | 引入版本 |
---|---|---|
[[noreturn]] | 表示函数不返回值,并只能应用在函数上面, 如果函数真的有返回值,那么该属性无效,并且会给出警告 |
c++11 |
[[carries_dependency]] | 表示消耗释放模式的依赖链在函数内外传播,允许编译器跳过一些不必要的栅栏指令 | c++11 |
[[deprecated]] [[deprecated("reason")]] |
表示某些实体已经废弃,或者因为某些原因废弃,可以用在类,定义类型名,变量,非静态数据成员,枚举,模板实例 | c++14 |
[[nodiscard]] | 表示被修饰的函数的返回值十分重要,或者枚举类型,对象类型十分重要不能被废弃 | c++17 |
[[maybe_unused]] | 抑制一下未使用变量的警告 | c++17 |
[[fallthrough]] | 出现在switch 语句中,抑制上一句case 没有break 而引起的fallthrough 的警告 |
|
[[noreturn]]
#include
[[ noreturn ]] void f() {
throw "error";
}
pp
[[ noreturn ]] void q(int i ) {
if ( i > 0 ) {
throw "positive";
}
}
测试代码
[[carries_dependency]]
该属性是个优化属性,它能够优化使用了memory_order_consume
的代码。它的作用就是转移使用memory_order_consume
而形成的依赖,将原先通过插入栅指令维持依赖树的方法变为有我们自己来编码维持依赖,从而达到优化的目的。如果一个通过memory_order_consume
的值被传递给一个函数的参数,那么如果没有使用[[carries_dependency]]
属性,那么编译器可能会使用一个内存栅指令去保证合适的memory order
语义的支持,如果一个参数被[[carries_dependency]]
注释,那么编译器就假定函数体会保存依赖,从而不必生成内存栅,达到优化的目的。同样如果一个函数的返回值以memory_order_consume
的方式读取,如果没有被[[carries_dependency]]
修饰,那么编译器需要插入一个栅指令来保证合适memory order
语义的支持。如果声明[[carries_dependency]]
,那么栅指令就不必要的了,依赖树转由调用者维持。
由上面也知道,[[carries_dependency]]
属性只能应用到函数或lamda表达式的参数处,或者返回值处。
[[deprecated]]/[[deprecated("reason)]
[[deprecated]]
void foo() {};
[[deprecated("推荐使用foo2")]]
void foo1(){};
void foo2(){};
int main(){
foo();
foo1
();
}
└[/mnt/D/Developer/WorkPlace/Personal-Workplace-Temp/C++/Amateur/src]> g++ -std=c++14 AttributeDeprecated.cpp -o ../bin/AttributeDeprecated
AttributeDeprecated.cpp: In function ‘int main()’:
AttributeDeprecated.cpp:14:3: warning: ‘void foo()’ is deprecated (declared at AttributeDeprecated.cpp:2) [-Wdeprecated-declarations]
foo(); // we're using a deprecated function
^
AttributeDeprecated.cpp:14:7: warning: ‘void foo()’ is deprecated (declared at AttributeDeprecated.cpp:2) [-Wdeprecated-declarations]
foo(); // we're using a deprecated function
^
AttributeDeprecated.cpp:15:3: warning: ‘void foo1()’ is deprecated (declared at AttributeDeprecated.cpp:6): 推荐使用foo2 [-Wdeprecated-declarations]
foo1();
^
AttributeDeprecated.cpp:15:8: warning: ‘void foo1()’ is deprecated (declared at AttributeDeprecated.cpp:6): 推荐使用foo2 [-Wdeprecated-declarations]
foo1();
^
注意
[[deprecated]]
修饰类和枚举类型是,声明在class
或者enum
前后有区别,如果声明在class
或者enum
之前,说明这个类或者枚举类型是废弃的; 反之,就说明这个变量是废弃的。而对于其他类型来说[[deprecated]]
只能放在类型定义符号之前。
[[nodiscard]]
[[nodiscard]]
出现在函数,枚举,结构体或者类声明中。如果一个函数声明为nodiscard
或者一个返回值为声明nodiscard
了枚举类型或者类的函数被一个discard-value
表达式调用,那么编译器将会报错。
[[nodiscard]] int something(){
return 1;
}
int main() {
int a = something();
}
AttributeNodiscard.cpp: In function ‘int main()’:
AttributeNodiscard.cpp:11:8: warning: unused variable ‘a’ [-Wunused-variable`]
int a = something();
^
[[maybe_unused]]
[[maybe_unused]]
主要用来修饰类的声明,类型定义,变量,非静态数据成员,函数,枚举类型,枚举器。 如果编译器对一些未使用的实体生成警告信息,这样的信息可以使用[[maybe_unused]]
属性抑制。
[[nodiscard]] int something() {
return 1;
}
int main() {
[[maybe_unused]] int a = something();
}
你会发现上面生成的警告信息已经被抑制了。
[[fallthrough]]
[[fallthrough]]
主要放在switch-case
语句中,位置放在case
语句之上。[[fallthrough]]
语句可以避免case
有表达式但是没有break
而导致的fallthrough
警告。
void something() {
}
void fallthrough() {
int i = 0;
switch(i) {
case 1:
case 2:
something();
case 3:
something();
case 4:
something();
default:
break;
}
}
AttributeFallthrough.cpp: In function ‘void fallthrough()’:
AttributeFallthrough.cpp:13:14: warning: this statement may fall through [-Wimplicit-fallthrough=]
something();
~~~~~~~~~^~
AttributeFallthrough.cpp:14:3: note: here
case 2 :
^~~~
AttributeFallthrough.cpp:15:14: warning: this statement may fall through [-Wimplicit-fallthrough=]
something();
~~~~~~~~~^~
AttributeFallthrough.cpp:17:3: note: here
case 3:
^~~~
AttributeFallthrough.cpp:18:14: warning: this statement may fall through [-Wimplicit-fallthrough=]
something();
~~~~~~~~~^~
AttributeFallthrough.cpp:20:3: note: here
case 4:
^~~~
void something() {
}
void fallthrough() {
int i = 0;
switch(i) {
case 1:
case 2:
something();
[[fallthrough]];
case 3:
something();
[[fallthrough]];
case 4:
something();
[[fallthrough]];
default:
break;
}
}
Note:你会发现上面的错误已经消失了。
生成fallthrought警告的条件
case
语句中有表达式但是没有break
语句,时编译器会产生fallthrough
警告。如果case
语句中没有任何表达式,编译器不会产生fallthrough
警告。如果case
语句中有表达式,但是以后的case
语句中没有语句,并最后的有break
结尾,那么编译器不会产生fallthrough
警告
void something() {
}
void fallthrough() {
int i = 0;
switch(i) {
case 1:
//1
case 2:
something(); //2
case 3:
something(); //3
case 4:
case 5:
default:
break;
}
}
1
处和3
处不会产生fallthrough
警告, 但是2
处会产生fallthrough
警告。
Note 要显示fallthrough
警告错误,g++编译器为7.0并且g++ 编译器需要添加一下编译参数-std=c++1z
, -Wextra
,
注意 这个属性一定要出现在函数的第一次声明中或者在任何翻译单元的一个属性中。否则程序将会报错。
#include
#include
std::atomic<int *> p;
void print( int * val )
{
std::cout<< *p << std::endl;
}
void print2(int * [[carries_dependency]] val)
{
std::cout << *p << std::endl;
}
int main () {
int * local = p.load(std::memory_order_consume);
if (local)
std::cout << *local<// 1
if (local)
print(local); //2
if(local)
print2(local); //3
}
1处的代码,依赖很清楚,所以编译器知道local
变量是废弃的,所以它必须确保依赖链被保存,避免内存栅的生成。
2处的代码,print
的定义是不透明的,所以编译器必须生成一个内存栅为了,读取*p
的值的print
函数能够返回正确的值。
3处的代码,编译器可以假定,就算print2
的定义也是不透明的,那么从参数道废弃的值的依赖被保存在指令流中,所以并不一定需要生成内存栅。很显然,print2
的定义确实保存了这个依赖,所以[[carries_dependency]]
对print2
的代码生成有一定的影响。