C++拾趣——编译器预处理宏__COUNTER__的应用场景

news2024/9/22 5:25:39

大纲

  • 生成唯一标识符
  • 调试信息
  • 宏展开
  • 模板元编程
  • 代码

在C++中,__COUNTER__是一个特殊的预处理宏,它主要被用来生成唯一的整数标识符。这个宏是由一些编译器(如GCC和Visual Studio)内置支持的,而不是C++标准的一部分。它的主要应用场景是在宏定义中,用于确保每次宏实例化时都能获得一个唯一的标识符,这在处理模板元编程、避免名称冲突或生成唯一标识符等场景中特别有用。

__COUNTER__宏每次被引用时,都会返回一个从0开始的连续递增的整数值。这意味着,在代码的不同部分或不同文件中使用__COUNTER__时,它都能保证生成唯一的整数。这对于在编译时生成唯一的变量名、函数名或枚举值等非常有帮助。

需要注意的是,因为__COUNTER__不是C++标准的一部分,所以它的具体行为可能因编译器而异。例如,某些编译器可能只在一个编译单元(translation unit)内保证__COUNTER__的唯一性,而另一些编译器则可能在整个程序中保证__COUNTER__的唯一性。

我们看下__COUNTER__的应用场景和事例:

生成唯一标识符

在需要唯一标识符的地方,__COUNTER__ 可以确保每次生成的值都是唯一的。

#include <iostream>
#include <thread>
#include <chrono>

// 定义宏,使用 __COUNTER__ 生成唯一的线程编号
#define THREAD_ID __COUNTER__

void threadFunction(int id) {
    while (true) {
        std::cout << "Thread [" << id << "] is running." << std::endl;
        std::this_thread::sleep_for(std::chrono::seconds(1));
    }
}

int main() {
    // 创建三个线程,每个线程都有唯一的编号
    std::thread t1(threadFunction, THREAD_ID);
    std::thread t2(threadFunction, THREAD_ID);
    std::thread t3(threadFunction, THREAD_ID);

    // 等待线程完成(在这个例子中,线程会一直运行)
    t1.join();
    t2.join();
    t3.join();

    return 0;
}

上面这段代码,创建3个线程时,我们希望每个线程有不同的ID。于是我们借助THREAD_ID的宏展开,让其值在编译期间确定为0~2,从而帮助我们避免手工设定0、1、2这样的数字。

需要注意的是,这段代码不能修改成For循环这类运行时生成线程的模式。因为__COUNTER__ 是在编译期间展开确定的,即编译器期间确定了它的值,在运行时时不改变的。
在这里插入图片描述

调试信息

在调试代码时,可以使用 __COUNTER__ 生成唯一的日志条目编号。这样会方便我们分析程序执行流程。

#include <iostream>
#include <string>

// 定义日志宏,使用 __COUNTER__ 生成唯一的日志条目编号
#define LOG_DEBUG(msg) \
    std::cout << "Log Entry [" << __COUNTER__ << "]: " << msg << std::endl;

void exampleFunction(int value) {
    LOG_DEBUG("Entering exampleFunction with value: " + std::to_string(value));

    if (value < 0) {
        LOG_DEBUG("Value is negative, returning early.");
        return;
    }

    LOG_DEBUG("Performing some operations...");
    // 模拟一些操作
    for (int i = 0; i < value; ++i) {
        LOG_DEBUG("Operation " + std::to_string(i));
    }

    LOG_DEBUG("Exiting exampleFunction.");
}

int main() {
    exampleFunction(3);
    exampleFunction(-1);
    exampleFunction(5);

    return 0;
}

在exampleFunction中,我们一共在5处打印了Log,其中有些Log是在分支中执行的,有些Log是在循环中执行的。我们可以通过添加了 __COUNTER__ 的日志快速确定代码的执行流程。
在这里插入图片描述
比如上图第1个流程中,就没有进入【1】这个分支;第2个流程,从【1】这个流程中直接退出了;第3个流程运行了所有“锚点”。

宏展开

在复杂的宏展开过程中,__COUNTER__可以确保生成的代码片段具有唯一性,避免命名冲突。

下面这个例子模拟《Robot Operating System——深度解析手动加载动态库的运行模式》中Node注册的流程。我们会通过静态变量自动初始化的特性,在其初始化过程中,自动注册若干继承于Base的子类对象。由于有多个子类需要注册,我们就需要多个静态变量。而我们希望有统一的方式来注册它们,这样就需要使用一种统一的方式生成不同的变量名。

我们先声明一个基类Base。它提供了一个纯虚方法display。

#ifndef BASE_H
#define BASE_H

class Base {
public:
    virtual ~Base() = default;
    virtual void display() const = 0;
};

#endif // BASE_H

然后提供一个工厂类用于注册对象。注意instance是个静态变量,这样不同地方调用Factory::instance()都会获得统一个对象,进而将不同的Base子类对象注册进来。

#ifndef FACTORY_H
#define FACTORY_H

#include <iostream>
#include <map>
#include <string>
#include <functional>
#include <memory> // for std::shared_ptr


#include "base.h"

class Factory {
public:
    using CreateFunc = std::function<std::shared_ptr<Base>()>;

    static Factory& instance() {
        static Factory instance;
        return instance;
    }

    void registerClass(const std::string& className, CreateFunc createFunc) {
        registry_[className] = std::move(createFunc);
    }

    std::shared_ptr<Base> create(const std::string& className) {
        auto it = registry_.find(className);
        if (it != registry_.end()) {
            return it->second();
        }
        return nullptr;
    }

private:
    std::map<std::string, CreateFunc> registry_;
};

#endif // FACTORY_H

继承于Base的子类只要实现display方法,然后通过REGISTER_CLASS来注册。

#ifndef DERIVED_A_H
#define DERIVED_A_H

#include <iostream>

#include "macro.h"
#include "base.h"

class DerivedA : public Base {
public:
    void display() const override {
        std::cout << "DerivedA instance" << std::endl;
    }
};


REGISTER_CLASS(DerivedA)

#endif // DERIVED_A_H

该宏的实现如下

#ifndef MACRO_H
#define MACRO_H

#include <memory>
#include <functional>
#include <string>

#include "factory.h"
#include "base.h"

#define REGISTER_CLASS(className) \
    REGISTER_CLASS_INTER(className, __COUNTER__)

#define REGISTER_CLASS_INTER(className, index) \
    REGISTER_CLASS_WITH_COUNTER(className, index)

#define REGISTER_CLASS_WITH_COUNTER(className, index) \
    class Factory##index { \
    public: \
        Factory##index() { \
            Factory::instance().registerClass(#className, []() -> std::shared_ptr<Base> { return std::make_shared<className>(); }); \
        } \
    }; \
    static Factory##index global_##index;

#endif // MACRO_H

它会自动生成静态变量global_0。这个变量的初始化时,会调用Factory0类的构造函数,从而将类的对象注册到Factory中。

如果此时我们再注册一个类

#ifndef DERIVED_B_H
#define DERIVED_B_H

#include <iostream>

#include "macro.h"
#include "base.h"

class DerivedB : public Base {
public:
    void display() const override {
        std::cout << "DerivedB instance" << std::endl;
    }
};

REGISTER_CLASS(DerivedB)

#endif // DERIVED_B_H

就会生成一个新的类Factory1和静态变量global_0。

然后我们就可以在Main函数中获取这些类的对象,然后加以使用

#include <memory> // for std::shared_ptr

#include "derived_a.h"
#include "derived_b.h"

int main() {
    Factory& factory = Factory::instance();

    std::shared_ptr<Base> a = factory.create("DerivedA");
    if (a) {
        a->display();
    } else {
        std::cout << "DerivedA not found" << std::endl;
    }

    std::shared_ptr<Base> b = factory.create("DerivedB");
    if (b) {
        b->display();
    } else {
        std::cout << "DerivedB not found" << std::endl;
    }

    return 0;
}

在这里插入图片描述

模板元编程

在 C++ 模板元编程中,__COUNTER__可以用于生成唯一的模板实例。

我们还是以注册类的为例,只是我们将宏中的类变成模板类。

#ifndef CLASS_FACTORY_H
#define CLASS_FACTORY_H

#include <string>
#include <memory>
#include <cxxabi.h> // for abi::__cxa_demangle

#include "factory.h"
#include "base.h"

template<class T, int N> 
class ClassFactory { 
public: 
    ClassFactory() { 
        Factory::instance().registerClass(getClassName(), []() -> std::shared_ptr<Base> { return std::make_shared<T>(); }); \
    } 
private:
    std::string getClassName() {
        const char* name = typeid(T).name();
        int status = 0;
        char* demangled = abi::__cxa_demangle(name, nullptr, nullptr, &status);
        std::string className = (status == 0) ? demangled : name;
        free(demangled);
        return className;
    }
}; 

#endif // CLASS_FACTORY_H

上例中比较少见的是abi::__cxa_demangle函数,它用于将编译器生成的类型名(mangled name)转换为人类可读的形式。然后我们将这个名字和指向子类的Base智能指针绑定。

这样我们的宏就会比较简单

#ifndef MACRO_H
#define MACRO_H

#include "class_factory.h"

#define REGISTER_CLASS(className) \
    REGISTER_CLASS_INTER(className, __COUNTER__)

#define REGISTER_CLASS_INTER(className, index) \
    DECLARE_GLOBAL(className, index)

#define DECLARE_GLOBAL(className, index) \
    static ClassFactory<className, index> global_##index;

#endif // MACRO_H

main函数和上例中一样,此处不表了。
在这里插入图片描述

代码

https://github.com/f304646673/cpulsplus/tree/master/counter

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2043427.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

《AI视频类工具之五——​ 开拍》

一.简介 官网:开拍 - 用AI制作口播视频用AI制作口播视频https://www.kaipai.com/home?ref=ai-bot.cn 开拍是一款由美图公司在2023年推出,利用AI技术制作的短视频分享应用。这款工具通过AI赋能,为用户提供了从文案创作、视频拍摄到视频剪辑、包装的一站式解决方案,极大地…

Using the ST-LINK/V2-1 to program and debug the STM32 on board

1. Using the ST-LINK/V2-1 to program and debug the STM32 on board To program the STM32 on the board, plug in the two jumpers on CN2 要对板上的STM32进行编程&#xff0c;请插入CN2上的两个跳线 2. 单片机供电 标有IDD的跳线JP6用于测量STM32微控制器的功耗 拆下跳…

UE5学习笔记14-动画的混合空间

零、我看视频中使用的是UE5.0左右的版本&#xff0c;我使用的是UE5.4&#xff0c;5.4中创建混合空间&#xff0c;没有看见有2D和3D混合动画空间的区分&#xff0c;具体的UE5如何创建2D的动画暂时不知道(我感觉现在创建的是3D的动画) 一、创建混合空间 1.我将所有的动画蓝图和动…

vue用户管理、角色管理和部门管理展示

1、用户和角色一对多&#xff0c;用户和部门多对多 2、用户管理 编辑用户时部门层级展示 角色-下拉框展示 <template><div class"s"><!-- 操作按钮 --><div class"shang"><el-input v-model"searchText" placeholde…

EXTI外部中断之对射式红外传感器计次应用案例

系列文章目录 STM32中断系统之EXTI外部中断 文章目录 系列文章目录前言一、应用案例简介二、电路接线图三、应用案例代码四、应用案例分析4.1 配置外部中断4.1.1 配置RCC时钟4.1.2 配置GPIO4.1.3 配置AFIO4.1.4 配置EXTI4.1.5 配置NVIC 4.2 编写中断函数 前言 提示&#xff1…

泛微OA系统走进腾讯大厦

企业信息化、数字化、网络化、智能化的快速发展带来了无限可能&#xff0c;但同时也带来了系统安全的严峻挑战。您准备好应对了吗? 上月由腾讯安全部、泛微联合举办的“OA 系统安全防护与腾讯iOA 零信任安全策略客户会”在腾讯滨海大厦成功举办&#xff0c;本次活动邀请了60位…

拆开一个断了FPC的墨水屏,是不是像OLED一样驱动芯片在里面

可对比查看一个OLED的屏幕拆解 拆解理由 第一次焊接驱动板时的fpc上下接问题&#xff0c;但焊接到板子上并没有达到正常的显示效果。PI补强也被撕下来。后来拔下来后发现金手指断裂。本来想用一个fpc排线连接在一起&#xff0c;但后来发现并没有达到理想效果&#xff0c;飞线…

【Python学习-UI界面】PyQt5 小部件14-QDock 子窗口

可停靠窗口是一个子窗口&#xff0c;可以保持浮动状态或附加到主窗口的指定位置。 QMainWindow类的主窗口对象保留了一块区域供可停靠窗口使用。该区域位于中央窗口部件周围。 可停靠窗口可以在主窗口内移动&#xff0c;也可以被取消停靠并由用户移动到新的区域。 样式如下: …

MinIO DataPOD 目标锁定 GPU Direct 并行文件系统

MinIO 推出针对 AI 应用的 DataPOD 参考架构 MinIO 设计了一种旨在为 AI 训练提供数据的 exascale DataPOD 参考架构。这家开源对象存储软件供应商正将其可扩展至100 PiB&#xff08;即大约112.6 PB&#xff09;的单元定位为一种替代方案&#xff0c;以取代使用 GPU Direct 技…

新中地2402期GIS特训营学员圆满结业,解锁GIS开发的无限可能!

GIS开发了解 24年8月5日&#xff0c;新中地GIS开发特训营2402期学员迎来了属于自己的结业典礼。 初入特训营&#xff0c;教与学双向奔赴 从24年3月4日开班&#xff0c;面对全新的领域&#xff0c;大家新中既有对未知的忐忑&#xff0c;更有对掌握GIS开发技术的期待 在本期学员…

车辆车载客流统计系统解决方案

车辆车载客流统计系统是一种用于实时监测和分析乘客流量的技术解决方案&#xff0c;它可以帮助公交公司、地铁运营商等交通管理部门优化运营计划、提高服务效率和乘客满意度。以下是一个详细的车载客流统计系统解决方案&#xff1a; 一、系统组成 传感器与设备 摄像头&#xf…

C库函数signal()信号处理

signal()是ANSI C信号处理函数&#xff0c;原型如下&#xff1a; #include <signal.h>typedef void (*sighandler_t)(int); sighandler_t signal(int signum, sighandler_t handler); signal()将信号signum的处置设置为handler&#xff0c;该handler为SIG_IGN&#xff…

脊髓损伤治疗方法和需要那些营养

脊髓损伤作为一种严重的神经系统损伤&#xff0c;其治疗与康复一直是医学界关注的重点。在中医领域&#xff0c;针对脊髓损伤的治疗有着独特的理论和方法&#xff0c;旨在通过调节人体内部环境&#xff0c;促进受损神经的修复与再生。以下将从中医缓解方法与营养支持两个方面进…

Velero 快速上手:使用 Velero 实现 Kubernetes 集群备份与迁移

一、veloro 简介 Velero 是vmware开源的一个云原生的灾难恢复和迁移工具&#xff0c;它本身也是开源的,采用Go语言编写&#xff0c;可以安全的备份、恢复和迁移Kubernetes集群资源数据&#xff1b;Velero 是西班牙语意思是帆船&#xff0c;非常符合Kubernetes社区的命名风格&a…

【Python快速入门和实践017】Python常用脚本-根据文件后缀对其进行分类保存

一、功能介绍 这段代码的功能是将源文件夹中的文件按照它们的文件扩展名分类并移动到不同的子文件夹中。步骤如下&#xff1a; 定义函数&#xff1a;move_files_by_extension函数接收两个参数&#xff1a; source_folder&#xff1a;源文件夹路径。destination_folder&#xff…

LLM + GraphRAG技术,赋能教育培训行业数字化创新

随着人工智能大模型时代的到来&#xff0c;LLM大语言模型、RAG增强检索、Graph知识图谱、Prompt提示词工程等技术的发展日新月异&#xff0c;也让各行各业更加期待技术带来的产业变革。 比如&#xff0c;教育培训行业&#xff0c;教师数量相对有限、学生个体差异较大&#xff…

数据结构第一天

数据结构基础知识 1.1 什么是数据结构 数据结构就是数据的逻辑结构以及存储操作 (类似数据的运算) 数据结构就教会你一件事&#xff1a;如何更有效的存储数据 1.2 数据 数据&#xff1a;不再是单纯的数字&#xff0c;而是类似于集合的概念。 数据元素&#xff1a;是数据的基本单…

怎样卸载python

python卸载干净的具体操作步骤如下&#xff1a; 1、首先打开电脑左下角开始菜单&#xff0c;点击“运行”选项&#xff0c;输入“cmd”。 2、输入“python --version”&#xff0c;得到一个程序的版本&#xff0c;按回车键。 3、点击下图程序。 4、然后在该页面中点击“uninst…

【投融界-注册安全分析报告】

前言 由于网站注册入口容易被黑客攻击&#xff0c;存在如下安全问题&#xff1a; 暴力破解密码&#xff0c;造成用户信息泄露短信盗刷的安全问题&#xff0c;影响业务及导致用户投诉带来经济损失&#xff0c;尤其是后付费客户&#xff0c;风险巨大&#xff0c;造成亏损无底洞…

8 自动类型转换、强制类型转换、整数数据溢出与模运算、浮点数精度丢失、类型转换值截断

目录 1 自动类型转换&#xff08;隐式转换&#xff09; 1.1 运算过程中的自动类型转换 1.1.1 转换规则 1.1.2 转换方向 1.1.3 案例演示 1.2 赋值时的自动类型转换 1.2.1 案例演示 2 强制类型转换&#xff08;显式转换&#xff09; 2.1 介绍 2.2 转换格式 2.3 转换规…