专业的编程技术博客社区

网站首页 > 博客文章 正文

C++20 新特性(22):C++ attribute的改进

baijin 2024-08-13 00:56:22 博客文章 6 ℃ 0 评论

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 ]] 属性进行了改进:

  1. 支持 [[ nodiscard ]] 属性增加参数,用于说明理由
  2. 对于构造函数也标记 [[ 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;
}


【往期回顾】

C++20 新特性(21):其他const相关的改进

C++20 新特性(20):constexpr的增强(续)

Tags:

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表