CppGuide社区 CppGuide社区
首页
  • 🔥最新谷歌C++风格指南(含C++17/20)
  • 🔥C++17详解
  • 🔥C++20完全指南
  • 🔥C++23快速入门
🔥C++面试
  • 第1章 C++ 惯用法与Modern C++篇
  • 第2章 C++开发工具与调试进阶
  • 第3章 C++多线程编程从入门到进阶
  • 第4章 C++网络编程重难点解析
  • 第5章 网络通信故障排查常用命令
  • 第6章 网络通信协议设计
  • 第7章 高性能服务结构设计
  • 第8章 Redis网络通信模块源码分析
  • 第9章 服务其他模块设计
  • 🚀 全部章节.pdf 下载 (opens new window)
  • 🔥C++游戏编程入门(零基础学C++)
  • 🔥使用C++17从零开发一个调试器 (opens new window)
  • 🔥使用C++20从零构建一个完整的低延迟交易系统 (opens new window)
  • 🔥使用C++从零写一个C语言编译器 (opens new window)
  • 🔥从零用C语言写一个Redis
  • leveldb源码分析
  • libevent源码分析
  • Memcached源码分析
  • TeamTalk源码分析
  • 优质源码分享 (opens new window)
  • 🔥远程控制软件gh0st源码分析
  • 🔥Windows 10系统编程
  • 🔥Linux 5.x内核开发与调试 完全指南 (opens new window)
  • TCP源码实现超详细注释版.pdf (opens new window)
  • 高效Go并发编程
  • Go性能调优
  • Go项目架构设计
  • 🔥使用Go从零开发一个数据库
  • 🔥使用Go从零开发一个编译器 (opens new window)
  • 🔥使用Go从零开发一个解释器 (opens new window)
Rust编程指南
  • SQL零基础指南
  • MySQL开发与调试指南
GitHub (opens new window)
首页
  • 🔥最新谷歌C++风格指南(含C++17/20)
  • 🔥C++17详解
  • 🔥C++20完全指南
  • 🔥C++23快速入门
🔥C++面试
  • 第1章 C++ 惯用法与Modern C++篇
  • 第2章 C++开发工具与调试进阶
  • 第3章 C++多线程编程从入门到进阶
  • 第4章 C++网络编程重难点解析
  • 第5章 网络通信故障排查常用命令
  • 第6章 网络通信协议设计
  • 第7章 高性能服务结构设计
  • 第8章 Redis网络通信模块源码分析
  • 第9章 服务其他模块设计
  • 🚀 全部章节.pdf 下载 (opens new window)
  • 🔥C++游戏编程入门(零基础学C++)
  • 🔥使用C++17从零开发一个调试器 (opens new window)
  • 🔥使用C++20从零构建一个完整的低延迟交易系统 (opens new window)
  • 🔥使用C++从零写一个C语言编译器 (opens new window)
  • 🔥从零用C语言写一个Redis
  • leveldb源码分析
  • libevent源码分析
  • Memcached源码分析
  • TeamTalk源码分析
  • 优质源码分享 (opens new window)
  • 🔥远程控制软件gh0st源码分析
  • 🔥Windows 10系统编程
  • 🔥Linux 5.x内核开发与调试 完全指南 (opens new window)
  • TCP源码实现超详细注释版.pdf (opens new window)
  • 高效Go并发编程
  • Go性能调优
  • Go项目架构设计
  • 🔥使用Go从零开发一个数据库
  • 🔥使用Go从零开发一个编译器 (opens new window)
  • 🔥使用Go从零开发一个解释器 (opens new window)
Rust编程指南
  • SQL零基础指南
  • MySQL开发与调试指南
GitHub (opens new window)
  • 第1章C++惯用法与ModernC++篇

    • 1.1 C++ RAII惯用法
    • 1.2 pimpl 惯用法
    • 1.3 拥抱C++新变化(C++11/14/17新增的实用特性)
    • 1.4 统一的类成员初始化语法与 std::initializer_list<T>
    • 1.5 C++17注解标签(attributes)
      • 1.6 final/override/=default/=delete语法
      • 1.7 auto关键字的前尘后事
      • 1.8 Range-based 循环语法
      • 1.9 C++17结构化绑定
      • 1.10 stl容器新增的实用方法介绍
      • 1.11 stl中的智能指针类详解
    • 第2章C++开发工具与调试进阶

    • 第3章C++多线程编程从入门到进阶

    • 第4章C++网络编程重难点解析

    • 第5章网络通信故障排查常用命令

    • 第6章网络通信协议设计

    • 第7章高性能服务结构设计

    • 第8章Redis 网络通信模块源码分析

    • 第9章 服务其他模块设计

    • C++后端开发进阶
    • 第1章C++惯用法与ModernC++篇
    zhangxf
    2023-04-03
    目录

    1.5 C++17注解标签(attributes)

    本小节介绍的属性标签(attributes)在其他语言中又叫注解(annotations),在C++98/03时代,不同的编译器使用不同的注解去为代码增加一些额外的说明,读者可能在各种C/C++代码中见过像**#pragma**、__declspec、__attribute等注解。然而不同的编译器对于同一功能可能使用不同的注解,这样导致我们需要为不同的编译器编写针对那个编译器的注解代码。从C++11开始,新的语言标准开始统一制定了一些常用的注解标签,本节我们来介绍一些比较常用的。使用注解标签的语法是:

    [[attribute]] types/functions/enums/etc
    
    1

    这些标签可用于修饰任意类型、函数或者enumeration,在C++17之前不能用于修饰命名空间(namespace)和enumerator,在C++17标准中这个限制也被取消了。

    # 1.5.1 C++98/03的enumeration和C++11的enumerator

    读者可能对enumeration和enumerator这两个词感到困惑,前者指的从 C 时代就存在的不限定作用域的枚举。例如下面的枚举类型就是一个enumeration:

    //一个enumeration的例子
    enum Color
    {
        black,
        white,
        red
    };
    
    //无法编译通过
    bool white = true;
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10

    这种枚举类型之所以称之为不限定作用域的枚举是因为一旦定义了这样一种枚举,在其所在的作用域内,不能再定义与之同名的变量了。例如如果定义了上述Color枚举,此时再定义一个名叫white的变量,是无法编译通过的。而enumerator指的是从C++11开始引入的、如下形式定义的枚举变量:

    //一个enumerator的例子
    enum class Color
    {
        black,
        white,
        red
    };
    
    //可以编译通过
    bool white = true;
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10

    此时,由于枚举值white对外部不可见(必须通过Color::white来引用),可以定义一个同名的white变量。这种枚举变量被称之为限定作用域的枚举。

    # 1.5.2 C++17的注解标签

    在分清楚enumeration和enumerator之后,让我们回到正题上来。

    C++11引入了的常用的注解标签有**[[noreturn]]**,这个注解的含义是告诉编译器某个函数没有返回值,例如:

    [[noreturn]] void terminate();
    
    1

    这个标签一般在设计一些系统函数时使用,例如std::abort()和std::exit()。

    C++14引入了**[[deprecated]]**标签用于表示一个函数或者类型等已经被弃用,当你使用这些被弃用的函数或者类型,编译时编译器会给出相应的警告,有的编译器直接产生编译错误。

    [[deprecated]] void funcX();
    
    1

    这个标签在实际开发中非常有用,尤其在设计一些库代码时,如果库的作者希望某个函数或者类型不想再被用户使用可以使用该标注标记。当然,你也可以使用以下语法给出编译时的具体警告或者出错信息:

    [[deprecated("use funY instead")]] void funcX();
    
    1

    有如下代码:

    #include <iostream>
    
    [[deprecated("use funcY instead")]] void funcX()
    {
        //实现省略...
    }
    
    int main()
    {
        funcX();
        return 0;
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12

    我在main函数中调用被标记为deprecated的函数funcX,在gcc/g++ 7.3中编译会得到如下警告信息:

    [root@myaliyun testmybook]# g++ -g -o test_attributes test_attributes.cpp 
    test_attributes.cpp: In function ‘int main()’:
    test_attributes.cpp:10:11: warning: ‘void funcX()’ is deprecated: use funcY instead [-Wdeprecated-declarations]
        funcX();
              ^
    test_attributes.cpp:3:42: note: declared here
    [[deprecated("use funcY instead")]] void funcX()
                                             ^~~~~
    
    1
    2
    3
    4
    5
    6
    7
    8

    对于Java开发者来说,这个标注应该是再熟悉不过了,Java中使用@Deprecated标注可以达到同样的作用,这大概是C++标准委员会拖欠广大C++开发者太久的一个特性吧,好在时至今日我们终于等到了。

    C++17提供了如下三个实用注解:

    • [[fallthrough]]
    • [[nodiscard]]
    • [[maybe_unused]]

    让我们逐一来介绍它们的用法。

    **[[fallthrough]]用于switch-case语句中,当某个case分支执行完毕后如果没有break语句,编译器可能会给出一条警告,但有时候这可能是开发者故意为之的,为了让编译器明确的知道开发者的意图,可以在需要某个case分支被“贯穿”处(上一个case没有break)显式设置[[fallthrough]]**标记。代码示例如下:

    switch (type)
    {
    case 1:
        func1();
        //这个位置缺少break语句,且没有fallthrough标注,
        //可能是一个逻辑错误,编译时编译器可能会给出警告,以提醒修改之
    case 2:
        func2();
        //这里也缺少break语法,但是使用了fallthrough标注,
        //说明是开发者有意为之,编译器不会给出任何警告   
    [[fallthrough]];
    case 3:
        func3();
    
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15

    注意,在gcc/g++中**[[fallthrough]]**后面的分号不是必须的,在Visual Studio中必须加上分号,否则无法编译通过。

    熟悉golang语言的读者,可能对fallthrough这一语法特性非常熟悉,golang中在switch-case后加上fallthrough是一个常用的告诉编译器意图的语法规则。例如:

    //以下是golang语法
    s := "abcd"
    switch s[3] {
        case 'a':
        	fmt.Println("The integer was <= 4")
        	fallthrough
        case 'b':
        	fmt.Println("The integer was <= 5")
        	fallthrough
        case 'c':
        	fmt.Println("The integer was <= 6")
        default:
        	fmt.Println("default case")
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14

    **[[nodiscard]]一般用于修饰函数,告诉函数调用者必须关注该函数的返回值(即不能丢弃该函数返回值)。如果调用者未将该函数的返回值赋值给一个变量,编译器会给出一个警告。例如假设有一个网络连接函数connect,我们通过返回值明确的说明了连接是否建立成功,为了防止调用者在使用时直接将其值丢弃,我们可以将该函数使用[[nodiscard]]**标注标记:

    [[nodiscard]] int connect(const char* address, short port)
    {
        //实现省略...
    }
    
    int main()
    {
        connect("127.0.0.1", 8888);
        
        return 0;
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11

    到C++20中对于诸如operator new()、std::allocate()等库函数均使用了**[[nodiscard]]**进行了标记,以强调必须使用这些函数的返回值。

    **[[maybe_unused]]**有些编译器会对程序代码中未被使用的函数或变量给出警告,在C++17之前,程序员们为了消除这些警告要么修改编译器警告选项设置,要么定义一个类似于UNREFERENCED_PARAMETER的宏来显式调用这些未使用的变量一次以消除编译警告。

    #define UNREFERENCED_PARAMETER(x) x
    
    int APIENTRY wWinMain(HINSTANCE hInstance,
                         HINSTANCE hPrevInstance,
                         LPWSTR    lpCmdLine,
                         int       nCmdShow)
    {
        UNREFERENCED_PARAMETER(hPrevInstance);
        UNREFERENCED_PARAMETER(lpCmdLine);
        
        //...无关代码省略
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12

    上述代码节选自一个标准的Win32程序的结构,其中函数参数hPrevInstance和lpCmdLine一般不会被用到,编译器会给出警告,为了消除这类警告,定义了一个宏UNREFERENCED_PARAMETER,并调用之,造成这两个参数被使用到的假象。

    有了**[[maybe_unused]]**注解之后,我们再也不需要这类宏来“欺骗”编译器了。上述代码使用该注解可以修改如下:

    int APIENTRY wWinMain(HINSTANCE hInstance,
                         [[maybe_unused]] HINSTANCE hPrevInstance,
                         [[maybe_unused]] LPWSTR    lpCmdLine,
                         int       nCmdShow)
    {    
        //...无关代码省略
    }
    
    1
    2
    3
    4
    5
    6
    7

    读者可以通过C++官方站点了解 C++ 新标准中更多的注解的用法。

    上次更新: 2025/04/01, 20:53:14
    1.4 统一的类成员初始化语法与 std::initializer_list<T>
    1.6 final/override/=default/=delete语法

    ← 1.4 统一的类成员初始化语法与 std::initializer_list<T> 1.6 final/override/=default/=delete语法→

    最近更新
    01
    C++语言面试问题集锦 目录与说明
    03-27
    02
    第四章 Lambda函数
    03-27
    03
    第二章 关键字static及其不同用法
    03-27
    更多文章>
    Copyright © 2024-2025 沪ICP备2023015129号 张小方 版权所有
    • 跟随系统
    • 浅色模式
    • 深色模式
    • 阅读模式