C++ attribute 简介
C++ 的 attribute 是一种标准的语法结构来对语言进行扩展,用来统一各种编译器的自定义扩展,例如 gcc 的 __attribute__((...)) ,或者微软的 __declspec() 。
C++ 语言本身定义了一些标准的 attribute ,例如 [[ noreturn ]] 、 [[ nodiscard ]] 等,也支持编译器特定的 attribute ,例如 [[ gnu::unused ]] 等,
C++20 对 [[ nodiscard ]] 的改进
在 C++17 中引入了 [[ nodiscard ]] 属性,主要是用来标记一个对象或者一个函数,通知编译器这个对象或者这个函数返回的对象不应该被丢弃,如果被丢弃了,编译器应该发出警告。
C++20 在 C++17 的基础上,对 [[ nodiscard ]] 属性进行了改进:
- 支持 [[ nodiscard ]] 属性增加参数,用于说明理由
- 对于构造函数也标记 [[ nodiscard ]] 属性
下面通过一些例子来说明 [[ nodiscard ]] 属性:
#include <iostream>
using std::cout, std::endl;
// <1> 类本身是 nodiscard ,函数返回这个类的对象时不可丢弃
struct [[ nodiscard("for example, maybe memory leak") ]] SA
{
int m_a;
};
struct SC
{
SC() : m_b( 7 ) {} // <2> 普通构造函数
[[ nodiscard ]] SC( int fd, int b ) : m_b ( b ) {} // <3> 特定的构造函数,构建的对象不可丢弃
int m_b;
};
// <21> 函数返回的对象不可丢弃
struct SA func_1()
{
return SA { 5 };
}
// <22> 返回的是对象的引用,而不是对象,不同的类型,因此不算不可丢弃
struct SA & func_2()
{
static SA a1 { 5 };
return a1;
}
// <23> 函数本身有nodiscard标记,不管返回什么对象都不可丢弃
[[ nodiscard ]] struct SC func_3()
{
return SC();
}
// <24> 普通函数,SC类也只是某些构造函数不可丢弃,不是整个类本身不可丢弃,因此返回的结果也不算不可丢弃
struct SC func_4()
{
return SC( 2, 3 );
}
int main( int argc, char * argv[] )
{
SA(); // <31> 构造不可丢弃对象,但又丢弃了,需要给出警告。(不过 gcc 10 未给出警告)
(void)SA(); // <32> 除非显式说明不关心返回值,不需要给出警告
SC(); // <33> 调用普通构造函数,不需要给出警告
SC( 3, 2 ); // <34> 调用 nodiscard 的构造函数,但又丢弃了,需要给出警告
(void)SC( 4, 3 ); // <35> 显式说明不关心返回值,不需要给出警告
struct SC a1 = SC( 5, 4 ); // <36> 构造给具体的有名字的对象,没有丢弃,不需要警告
func_1(); // <41> 函数返回不可丢弃对象,但又丢弃了,需要给出警告
struct SA a2;
a2 = func_1(); // <42> 函数返回值赋值给对象,没有丢弃,不需要给出警告
func_2(); // <43> 函数返回不可丢弃对象的引用,不是不可丢弃对象,类型不同,不需要给出警告
func_3(); // <44> 函数本身有不可丢弃标记,任何类型的返回值都不可丢弃,需要给出警告
struct SC a3 = func_3(); // <45> 返回值没有丢弃,不需要给出警告
func_4(); // <46> 普通函数,不需要给出警告
cout << a1.m_b << " " << a2.m_a << " " << a3.m_b << endl;
return 0;
}
编译和运行结果为:
[smlc@test code]$ g++ -std=c++20 a22.cpp
a22.cpp: In function ‘int main(int, char**)’:
a22.cpp:51:11: warning: ignoring return value of ‘SC::SC(int, int)’, declared with attribute ‘nodiscard’ [-Wunused-result]
51 | SC( 3, 2 ); // <34> 调用 nodiscard 的构造函数,但又丢弃了,需要给出警告
| ^
a22.cpp:17:18: note: declared here
17 | [[ nodiscard ]] SC( int fd, int b ) : m_b ( b ) {} // <3> 特定的构造函数,构建的对象不可丢弃
| ^~
a22.cpp:55:9: warning: ignoring returned value of type ‘SA’, declared with attribute ‘nodiscard’: ‘for example, maybe memory leak’ [-Wunused-result]
55 | func_1(); // <41> 函数返回不可丢弃对象,但又丢弃了,需要给出警告
| ^
a22.cpp:22:11: note: in call to ‘SA func_1()’, declared here
22 | struct SA func_1()
| ^~~~~~
a22.cpp:9:58: note: ‘SA’ declared here
9 | struct [[ nodiscard("for example, maybe memory leak") ]] SA
| ^~
a22.cpp:59:9: warning: ignoring return value of ‘SC func_3()’, declared with attribute ‘nodiscard’ [-Wunused-result]
59 | func_3(); // <44> 函数本身有不可丢弃标记,任何类型的返回值都不可丢弃,需要给出警告
| ^
a22.cpp:35:27: note: declared here
35 | [[ nodiscard ]] struct SC func_3()
| ^~~~~~
[smlc@test code]$ ./a.out
4 5 7
新增 likely 和 unlikely 属性
C++20 新增了likely 和 unlikely 属性,可以应用在 case 语句中,以及非定义变量的其他普通语句(statement)中,用来说明这个语句所在的代码执行路径,在运行时被执行的可能性会更高或更低,以便编译器可以有针对性的进行优化,达到命中率更高的执行路径分支预测,提高执行效率。
#include <iostream>
using std::cout, std::endl;
void func_1( int n )
{
switch( n )
{
[[ unlikely ]] case 1 : // <1> 通知编译器此 case 语句执行概率较低
cout << "1" << endl;
break;
case 2 :
cout << "2" << endl;
break;
[[ likely ]] case 5 :
[[ unlikely ]] cout << "5" << endl;
[[ likely ]] cout << "5 again" << endl;
// <2> 上面的同一个路径下不同语句分别有 likely 和 unlikely ,语法上允许,语义上标准未明确此路径以哪个为准,实际代码不应该出现这样的矛盾
break;
default :
// <3> 同一个语句不应该同时有 likely 和 unlikely ,unlikely会被忽略,编译器会给出警告
[[ likely ]] [[ unlikely ]] cout << "99 : ( " << n << " )" << endl;
break;
}
if( n > 3 ) [[ likely ]] { // <4> if 的这个分支概率更高,可以将 likely 放在这个位置
[[ likely ]] ; // <5> 也可以将 likely 放在路径里面,可以对空语句进行修饰
cout << "> 3" << endl;
}
else {
[[ unlikly ]] int a = 3; // <6> 不支持对定义变量的语句进行修饰,likely 或 unlikely 会被忽略,编译器会给出警告
cout << "not > " << a << endl;
}
return;
}
int main( int argc, char * argv[] )
{
func_1( 5 );
return 0;
}
用于测试是否支持某个属性的宏:__has_cpp_attribute
为了便于测试是否支持某个属性,C++20 中新增了一个宏 __has_cpp_attribute,可以检查某个属性是否支持,支持到哪个版本,这个宏的值是一个表示年月的整数。例如 nodiscard 属性的测试宏 __has_cpp_attribute( nodiscard ) 对于C++17(不支持reason参数)取值是201603,对于C++20(支持reason参数)取值是201907。应用程序可以根据宏的取值来判断支持程度,编写相应的代码。
除了测试是否支持某个属性外,C++20还定义了很多宏,用于判断是否支持某个语言特性,或者是否支持某个基础库,以便应用程序根据编译器和基础库的不同能力,执行不同的代码。
下面是一些例子:
#include <iostream>
using std::cout, std::endl;
// <1> 首先检查是否支持检测属性的宏
#ifdef __has_cpp_attribute
// <2> 然后根据属性的具体的值,来判断支持到什么程度
// 例如 nodiscard 的值是 201907,表示支持C++20中增强的带 reason 参数的 nodiscard 属性
#if __has_cpp_attribute( nodiscard ) >= 201907
[[ nodiscard( "should not discard this int" ) ]] int func_1()
{
cout << "nodiscard : " << __has_cpp_attribute( nodiscard ) << endl;
return 5;
}
// <3> 例如 nodiscard 的值是 201603,表示支持C++17中不带 reason 参数的 nodiscard 属性
#elif __has_cpp_attribute( nodiscard ) >= 201603
[[ nodiscard ]] int func_1()
{
cout << "nodiscard : " << __has_cpp_attribute( nodiscard ) << endl;
return 5;
}
#else
// <4> 支持 __has_cpp_attribute 宏,但不支持 nodiscard 属性,(实际不应该出现,nodiscard 比 __has_cpp_attribute 还要早)
int func_1()
{
cout << "nodiscard : " << __has_cpp_attribute( nodiscard ) << endl;
return 5;
}
#endif
#else
// <5> 不支持 __has_cpp_attribute 宏
int func_1()
{
// cout << "nodiscard : " << __has_cpp_attribute( nodiscard ) << endl;
return 5;
}
#endif
// <6> 先判断是否支持检查头文件,再判断某个头文件是否存在,存在了再包含此头文件
#ifdef __has_include
#if __has_include( <chrono> )
#include <chrono>
#endif
#endif
int main( int argc, char * argv[] )
{
func_1();
// <7> 对于不支持的属性,__has_cpp_attribute() 宏返回值是 0
cout << "__has_cpp_attribute( nonexist ) : " << __has_cpp_attribute( nonexist ) << endl;
// <8> 检查语言特性的宏,是否支持 countexpr 语句
#ifdef __cpp_constexpr
cout << "__cpp_constexpr : " << __cpp_constexpr << endl;
#else
cout << "__cpp_constexpr : (undefined)" << endl;
#endif
// <9> 检查基础库特性的宏,是否包含了 <chrono> 基础库
#ifdef __cpp_lib_chrono
cout << "__cpp_lib_chrono : " << __cpp_lib_chrono << endl;
#else
cout << "__cpp_lib_chrono : (undefined)" << endl;
#endif
return 0;
}
【往期回顾】
本文暂时没有评论,来添加一个吧(●'◡'●)