1.初识属性
1.1 实验A: noreturn 属性
1 [[ noreturn ]] static void 2 thread1(void *data){ 3 cout << "noreturn " << endl; 4 while(1){ 5 this_thread::sleep_for(2s); 6 cout << "sleeping " << endl; 7 } 8 }
结果:
noreturn
sleeping
sleeping
sleeping
...
其中的[[ noreturn ]] 就是属性列表,其中 [[ ... ]] 内可以有多个属性,noreturn这个属性告诉编译器,这个函数不返回,如果在代码中返回了(正常执行完毕或者用return),那么编译产生警告。
1.2 标准属性
C++ 标准仅定义下列属性。
属性 |
版本 |
修饰目标 | 作用 |
[[noreturn]] |
C++11 |
函数 |
指示函数不返回,没有return语句,不正常执行完毕,但是可以通过出异 常或者exit()函数退出。 |
[[carries_dependency]] |
C++11 |
函数、变量 | 指示释放消费 std::memory_order 中的依赖链传入和传出该函数。 |
[[deprecated("reason")]] |
C++14 |
函数、变量、类等 |
声明有此属性的名称或实体被弃用,"reason"是原因,支持中文,原因可为空。 |
[[fallthrough]] |
C++17 |
case语句 | 声明前一 个case 标号没有break是有意的,此时编译器不需要发出警告。 |
[[nodiscard]] |
C++17 |
函数 | 若返回值被舍弃,则鼓励编译器发布警告。 |
[[maybe_unused]] |
C++17 |
函数、变量、类等 | 使用该属性的标识符,即使未使用,编译器也不发出警告。 |
[[likely]] [[unlikely]] |
C++20 |
标号、语句(非声明语句) |
likely指示编译器应该对该语句的执行路径比其他执行路径进行更多的优化。 unlikely与之相反。 |
[[no_unique_address]] |
C++20 |
非位域、非静态数据成员 | 非静态数据成员不需要拥有不同于其类的所有其他非静态数据成员的地址。 |
[[expects]] [[ensures]] [[assert]] |
C++20 |
函数 | 为函数指定契约属性 |
[[optimize_for_synchronized]] |
TM TS |
函数 | 应该针对来自 synchronized 语句的调用来优化该函数定义 |
编译器厂商也可以自定义属性。例如 GNU 的 [[hot]]、[[always_inline]],微软的 [[__declspec(xxx)]]
等。
2.语法
2.1 属性列表语法
[[
命名空间1::属性1
(参数列表
)
,...命名空间n::属性n
(参数列表
)
]] alignas(N)
(C++11 起)[[
using
命名空间X:
属性1(参数列表
)
,...属性n(参数列表
)
]]
(C++17 起)[[
契约属性记号 契约等级(可选) 标识符(可选):
表达式]]
(C++20 起)
其中
- 属性可用在 C++ 程序中的几乎所有声明语句中:类型、变量、函数、代码块、整个翻译单元,命名空间除外。
- 每个特定的属性都仅在使用处有效.所有未定义的属性均被忽略,且不产生错误。 (C++17 起)
- 属性可出现在整个声明语句之前、或直接跟在被声明实体的名字之后,大多数其他情形中,属性应用于直接位于其之前的实体。
- 第1条语法中,如果属性在当前命名空间或者使用了using namespace xxx,xxx是属性所在命名空间,命名空间可以省略,属性没有参数时参数也略。
- 第1条语法中,alignas(N),N为0或者非2的幂时无效。也可以省略掉alignas使用默认对齐。
- 表1.2中c++定义的标准属性并不是std命名空间下的。无需加std:: .
- 第2条语法中,属性1...n都是命名空间X内的,且属性前不用(也不可以)像语法1一样指定命名空间。
- 第3条见 3.契约属性
2.2 实验B:使用未定义的属性
1 [[ hhahahahahahaha ]] int //error,使用未定义的属性 2 fun1(void*){ 3 cout << " not return int" << endl; 4 }
结果,属性被忽略并产生一个警告。
attr.cpp:20:11: warning: ‘hhahahahahahaha’ attribute directive ignored [-Wattributes]
fun1(void*){
2.3 实验C:属性的位置
- 属性与变量:在声明语句最前面或者在变量名后面第1个位置.
正确语法:
1 [[deprecated]] int n1 = 100; //ok 2 int n2 [[deprecated]] = 100; //ok 3 4 int && ref2 [[deprecated]] = 1; //ok 5 int nn = 100; 6 [[deprecated]]int *p1 = & nn; //ok
错误语法:
1 int [[deprecated]] n3 = 100; //error, 2 // int n3 = 100 [[deprecated]]; //error, 3 int && [[deprecated]] ref1 = 1; //error
错误结果:
attr.cpp:136:9: warning: attribute ignored [-Wattributes]
int [[deprecated]] n3 = 100; //error,
^
attr.cpp:136:9: note: an attribute that appertains to a type-specifier is ignored
attr.cpp:138:27: warning: attribute ignored [-Wattributes]
int && [[deprecated]] ref1 = 1; //error
^~~~
attr.cpp:138:27: note: an attribute that appertains to a type-specifier is ignored
当有const,static,extern,register,auto修饰时也是这个规则。第7行是错误的。
1 //有static,extern,register,auto、const等修饰时 2 [[deprecated]] const static int n5 = 1; //ok, 3 [[deprecated]] extern int n6; //ok, 4 [[deprecated]] register char n7; //c++11 ok,c++17 error,c++17不支持register存储类别了。 5 [[deprecated]] auto n8 = 10; //ok, 6 7 static [[deprecated]] int n4 = 10; //error,
当变量是成员变量、全局变量、形式参数时,也是这个规则。第13行错误。
1 //属性与外部变量 2 [[deprecated]] extern int g_count ; //ok 3 //属性与形参 4 void 5 foo([[deprecated]] int arg1,int arg2 [[deprecated]],int arg3 [[deprecated]] = 10){ 6 } 7 8 //属性与成员变量 9 class ATT{ 10 public: 11 [[deprecated]] int a1 ; 12 int a2 [[deprecated]] ; 13 int [[deprecated]] a3 ; //error 14 };
- 属性与函数:
- 在声明语句最前面或者在函数名后面第1个位置.
- 契约属性和上面的不一样,它在参数之后,见 3.契约属性(c++20)
正确语法
1 #define REASON "使用cout" 2 [[deprecated(REASON)]] void fun1(){} //ok,属性声明在语句最前面 3 4 void fun2 [[deprecated(REASON)]](){} //ok,属性在声明实体后+1个位置 5 6 [[ noreturn ]] static void fun3(){throw 1;} //ok 7 8 inline void fun4 [[ noreturn ]](){throw 1;} //ok 9 10 [[nodiscard]] extern int fun5 [[deprecated]](){return 1;}//ok
错误语法
1 int [[nodiscard]]fun6(){ } //error,属性在声明实体前1个位置 2 void fun7() [[nodiscard]]{ } //error,属性在声明实体后+2个位置起
成员函数友元函数同样是这个规则
1 class FATTR{ 2 [[nodiscard]] int fun1(){} //ok 3 int [[nodiscard]] fun2() {} //error,属性在声明实体前1个位置 4 [[nodiscard]] friend int friend1() {} //ok 5 friend int friend2 [[nodiscard]]() {} //ok 6 //friend int [[nodiscard]]() friend3 {} //error. 7 };
多个属性
1 [[ noreturn,deprecated(U"utf32") ]] void //同时声明多个属性 2 fun8(){ 3 cout << "同时声明多个属性" << endl; 4 throw 1; 5 } 6 [[gnu::always_inline]] [[gnu::hot]] [[gnu::const]] [[nodiscard]] 7 inline int fun9(bool ){ //分开声明多个属性 8 9 }
- 属性与类、结构,联合、枚举:只能在关键字class、struct、union、enum和名字之间使用属性。
正确语法:
class [[deprecated]] A{}; //ok,标准写法,在class之后,类名之前。这个属性声明A过期了。在使用A的地方有警告提示。
错误语法:
1 [[deprecated]] class B{}; //error,在声明语句之前。 2 //class C [[deprecated]] {}; //error,属性在类的后面编译不过。 3 class D{}[[deprecated]] ; //error, 4 5 class E[[deprecated]] ; //ok,虽然不会警告,但是在定义E的时候,已经失去了deprecated的含义。 6 class E {};
struct、enum、unio同理
1 //union u1 [[deprecated]]{}; //error 2 //struct s1[[deprecated]]{}; //error 3 4 enum [[deprecated]] E1 {}; //ok 5 //[[deprecated]] enum E2 {}; //error,
- 属性与命名空间:命名空间不可以使用属性
1 //namespace [[deprecated]] N1{}; //error,命名空间不可以使用属性 2 //namespace N2 [[deprecated]]{}; //error,不可以在命名空间名字后面 3 //[[deprecated]] namespace N3 {}; //error,编译不过
2.4 实验D:属性声明using写法
正确语法:
[[using NS: opt(1), debug]] int i1; // ok:同 [[NS::opt(1), NS::debug]]
- 使用using NS后,不必再使用NS::属性这种语法
[[using NS: NS::opt(1)]] int io; // error:使用using NS后,不必再使用NS::属性这种语法
- 在使用using的[[ ]] 内,不可以有其它命名空间
[[using NS: opt(1), debug,gnu::hot]] int i2; // error:不可以有其它命名空间
- 其中的属性必需同时是命名空间NS下的属性或者标准属性,如
[[using NS: opt(1), debug,deprecated]]int i3; // ok:但是可以有标准的属性
- 如果想同时声明多个属性又想使用using语法:那要分开声明属性
[[using NS: opt(1), debug]] int i5 [[using gnu:const,hot,always_inline]];// ok:分开使用using
3.契约属性(c++20)
3.1 作用
为函数指定:
- 在被调用前参数应该满足的条件、
- 执行完毕后返回值应该満的条件和
- 函数执行过程中语句的断言。
如果条件不满足,则指定的违约处理函数被执行。
3.2 语法
[[
expects
契约等级(可选):
表达式]]
(C++20 起)[[
ensures
契约等级(可选) 标识符(可选):
表达式]]
(C++20 起)[[
assert
契约等级(可选):
表达式]]
(C++20 起)
其中:
- 契约等级 ,值是 default、audit 、axiom 之一;默认为 default
- 表达式是按语境转换成 bool 的表达式,且其顶层运算符不能是赋值或逗号运算符。
- 表达式运行是不能有副作用。
- 若表达式有异常,则std::terminate()被调用。
- 如想使用契约属性,函数的首个声明必须指定它,后续的声明与定义要与它保持一致。
- 如果友元使用契约属性,则该声明必须是定义且该编译单元唯一的声明。
- 第2条中:标识符用来代表函数返回值的标识符
- 第2条中:当契约等级 与 标识符 产生歧义时,按契约等级解理。
3.3 违约处理
如果契约条件不满足,则违约处理函数被执行。它的声明如下:
void (const std::contract_violation &) noexcept;
其中
contract_violation是std下的一个类,
描述关于契约违规的信息,如:行号、文件名、函数名、契约等级等等。- 这个违约函数通常由编译器实现。
用户可以指定违约处理函数执行完毕后程序继续的模式:
- 不断续执行,调用 std::terminate() 退出 。(默认)
- 断续执行。
3.4 实验E:契约属性
由于c++20还没有正式发布,现在(2019/07/23) gcc 9.1.1还不支持这个特性。
1 void 2 fcv_catch(const std::contract_violation & cv) noexcept{ 3 4 } 5 6 void fun100(int ); 7 8 void fun100(int); 9 10 void fun100(int){ 11 12 } 13 14 int fcv(int i) [[expects: i > 0]] [[ensures audit x: x < 1]] ; //ok 15 16 int fcv(int n) [[expects: n > 0]] [[ensures audit y: y < 1]] ; //ok 17 18 [[ensures audit y: y < 1]] int fcv2(int n) [[expects: n > 0]]; //error 19 20 class FCV{ 21 22 friend int f3(int n) [[expects: n > 0]] ; //error,不是定义 23 friend int fop(int n) [[expects: n > 0]] { //ok 24 25 } 26 } 27 28 int fcv(int i) [[expects: i > 0]] [[ensures audit x: x < 1]] { 29 i++; 30 float x = 0; 31 return x; 32 }
4. 查寻属性是否存在
__has_cpp_attribute(xxx) 这个宏可以查寻属性是否存在。
1 void 2 fu(){ 3 4 #if __has_cpp_attribute(opt) 5 cout << "has opt" << endl; 6 #else 7 cout << "not found opt" << endl; 8 #endif 9 10 }