百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术分析 > 正文

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

liebian365 2024-12-27 16:16 24 浏览 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的增强(续)

相关推荐

“版本末期”了?下周平衡补丁!国服最强5套牌!上分首选

明天,酒馆战棋就将迎来大更新,也聊了很多天战棋相关的内容了,趁此机会,给兄弟们穿插一篇构筑模式的卡组推荐!老规矩,我们先来看10职业胜率。目前10职业胜率排名与一周前基本类似,没有太多的变化。平衡补丁...

VS2017 C++ 程序报错“error C2065:“M_PI”: 未声明的标识符&quot;

首先,程序中头文件的选择,要选择头文件,在文件中是没有对M_PI的定义的。选择:项目——>”XXX属性"——>配置属性——>C/C++——>预处理器——>预处理器定义,...

东营交警实名曝光一批酒驾人员名单 88人受处罚

齐鲁网·闪电新闻5月24日讯酒后驾驶是对自己和他人生命安全极不负责的行为,为守护大家的平安出行路,东营交警一直将酒驾作为重点打击对象。5月23日,东营交警公布最新一批饮酒、醉酒名单。对以下驾驶人醉酒...

Qt界面——搭配QCustomPlot(qt platform)

这是我第一个使用QCustomPlot控件的上位机,通过串口精确的5ms发送一次数据,再将读取的数据绘制到图表中。界面方面,尝试卡片式设计,外加QSS简单的配了个色。QCustomPlot官网:Qt...

大话西游2分享赢取种族坐骑手办!PK趣闻录由你书写

老友相聚,仗剑江湖!《大话西游2》2021全民PK季4月激燃打响,各PK玩法鏖战齐开,零门槛参与热情高涨。PK季期间,不仅各种玩法奖励丰厚,参与PK趣闻录活动,投稿自己在PK季遇到的趣事,还有机会带走...

测试谷歌VS Code AI 编程插件 Gemini Code Assist

用ClaudeSonnet3.7的天气测试编码,让谷歌VSCodeAI编程插件GeminiCodeAssist自动编程。生成的文件在浏览器中的效果如下:(附源代码)VSCode...

顾爷想知道第4.5期 国服便利性到底需优化啥?

前段时间DNF国服推出了名为“阿拉德B计划”的系列改版计划,截至目前我们已经看到了两项实装。不过关于便利性上,国服似乎还有很多路要走。自从顾爷回归DNF以来,几乎每天都在跟我抱怨关于DNF里面各种各样...

掌握Visual Studio项目配置【基础篇】

1.前言VisualStudio是Windows上最常用的C++集成开发环境之一,简称VS。VS功能十分强大,对应的,其配置系统较为复杂。不管是对于初学者还是有一定开发经验的开发者来说,捋清楚VS...

还嫌LED驱动设计套路深?那就来看看这篇文章吧

随着LED在各个领域的不同应用需求,LED驱动电路也在不断进步和发展。本文从LED的特性入手,推导出适合LED的电源驱动类型,再进一步介绍各类LED驱动设计。设计必读:LED四个关键特性特性一:非线...

Visual Studio Community 2022(VS2022)安装图文方法

直接上步骤:1,首先可以下载安装一个VisualStudio安装器,叫做VisualStudioinstaller。这个安装文件很小,很快就安装完成了。2,打开VisualStudioins...

Qt添加MSVC构建套件的方法(qt添加c++11)

前言有些时候,在Windows下因为某些需求需要使用MSVC编译器对程序进行编译,假设我们安装Qt的时候又只是安装了MingW构建套件,那么此时我们该如何给现有的Qt添加一个MSVC构建套件呢?本文以...

Qt为什么站稳c++GUI的top1(qt c)

为什么现在QT越来越成为c++界面编程的第一选择,从事QT编程多年,在这之前做C++界面都是基于MFC。当时为什么会从MFC转到QT?主要原因是MFC开发界面想做得好看一些十分困难,引用第三方基于MF...

qt开发IDE应该选择VS还是qt creator

如果一个公司选择了qt来开发自己的产品,在面临IDE的选择时会出现vs或者qtcreator,选择qt的IDE需要结合产品需求、部署平台、项目定位、程序猿本身和公司战略,因为大的软件产品需要明确IDE...

Qt 5.14.2超详细安装教程,不会来打我

Qt简介Qt(官方发音[kju:t],音同cute)是一个跨平台的C++开库,主要用来开发图形用户界面(GraphicalUserInterface,GUI)程序。Qt是纯C++开...

Cygwin配置与使用(四)——VI字体和颜色的配置

简介:VI的操作模式,基本上VI可以分为三种状态,分别是命令模式(commandmode)、插入模式(Insertmode)和底行模式(lastlinemode),各模式的功能区分如下:1)...

取消回复欢迎 发表评论: