当前位置: 首页 > news >正文

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

大纲

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

在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_Hclass 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

相关文章:

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

大纲 生成唯一标识符调试信息宏展开模板元编程代码 在C中&#xff0c;__COUNTER__是一个特殊的预处理宏&#xff0c;它主要被用来生成唯一的整数标识符。这个宏是由一些编译器&#xff08;如GCC和Visual Studio&#xff09;内置支持的&#xff0c;而不是C标准的一部分。它的主要…...

使用HTML和cgi实现网页登录功能

0.HTML文件结构 一.HTML文件 1.test.html <!DOCTYPE html> <html><head><meta charset"utf-8"><title>菜鸟教程(runoob.com)</title></head><body><!-- 将结果提交给/cgi-bin/test.cgi下 --><form actio…...

Java流程控制01:用户交互Scanner

本节教学视频链接&#xff1a;https://www.bilibili.com/video/BV12J41137hu?p33&vd_sourceb5775c3a4ea16a5306db9c7c1c1486b5https://www.bilibili.com/video/BV12J41137hu?p33&vd_sourceb5775c3a4ea16a5306db9c7c1c1486b5 Scanner 类用于扫描输入文本从字符串中提…...

什么是回滚

回滚&#xff08;Rollback&#xff09;是指当程序或数据出现错误时&#xff0c;将程序或数据恢复到最近一个正确版本或上一次正确状态的行为。回滚机制在软件开发、数据库管理、系统部署等多个领域都有广泛应用&#xff0c;旨在保证系统的稳定性和数据的完整性。以下是关于回滚…...

Java项目通过IDEA远程debug调试

前言 在我们真实项目开发过程中&#xff0c;又是经常会发现一种问题&#xff0c;就是我们在开发环境功能是正常的&#xff0c;在测试环境可能也不太容易发现问题。 结果到了生产环境&#xff0c;由于数据量大&#xff0c;且数据类型变多后&#xff0c;就产生了一些比较难复现…...

Python 绘图入门

数据可视化的概念及意义 数据可视化有着久远的历史&#xff0c;最早可以追溯至10世纪&#xff0c;至今已经应用和发展了数百年。不知名的天文学家是已知的最早尝试以图形方式显示全年当中太阳&#xff0c;月亮和行星的位置变化的图。 图1 数据可视化的发展历程 什么是数据可视…...

RK3568平台(背光篇)背光驱动代码分析

一.背光驱动设备树DTS backlight: backlight {compatible "pwm-backlight";pwms <&pwm1 0 5555555 1>;brightness-levels <77 77 78 78 79 79 80 8182 83 84 85 86 87 87 8888 89 90 90 91 91 92 9394 94 95 95 96 96 9…...

华为od统一考试B卷【比赛】python实现

def split_params(param_str): return list(map(int, param_str.split(,))) def main(): # 获取输入 target_str input().strip() # 输入验证&#xff0c;拆分并转换为整数 try: m, n split_params(target_str) except ValueError: print(-1) return # 检查 M 和 …...

Prometheus 监控接入规范

目录 一、目的 二、自定义监控指标定义规范 2.1 基本命名规范 2.1.1 指标命名规范 2.1.2 标签名称 2.2 控制基数 2.2.1 避免高基数标签 2.2.2 预定义标签集 2.2.3 动态数据的处理 2.2.4 评估与监控基数 2.2.5 降低历史数据的保留 2.2.6 适当使用 Histogram 和 Summa…...

优化 SQL 查询性能:深入理解 EXPLAIN 命令

优化 SQL 查询性能:深入理解 EXPLAIN 命令 在 MySQL 数据库管理中,优化 SQL 查询性能是确保高效数据处理的关键。EXPLAIN 命令是分析和优化 SQL 查询的强大工具,它帮助我们理解查询执行计划,从而找到性能瓶颈并进行优化。本文将详细解释 EXPLAIN 命令返回的各个列的含义,…...

@Mapper报红

检查pom.xml&#xff0c;导入 org.mybatis.spring.boot 依赖&#xff1a; <dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>3.0.3</version></dependency…...

shell综合小实验1-----查看系统硬件信息

echo命令的使用 1&#xff1a;echo -n 不换行 echo -n “我是个大聪明” #不换行输入我是大聪明 2&#xff1a;echo -e 开启颜色 echo -e "\03335m我是大聪明\033[0m" #用35m这种颜色输出我是大聪明然后关闭颜色显示&#xff0c; 30多是字体颜色&#xff0c;40多是…...

【过程管理】项目需求管理规程(Word原件)

在软件开发的过程中&#xff0c;开发人员与用户之间往往忽视有效的信息沟通&#xff0c;这常常导致开发出的软件无法满足用户的实际需求&#xff0c;进而引发不必要的返工。返工不仅为开发人员带来技术上的困扰&#xff0c;增加了人力和物力的消耗&#xff0c;还会对软件的整体…...

C# 不使用 `async` 和 `await` 的常见场景

虽然 async 和 await 是强大的异步编程工具&#xff0c;但在某些情况下&#xff0c;不使用它们可能更合适。以下是一些不使用 async 和 await 的常见场景&#xff1a; 方法是完全同步的&#xff1a; 如果方法中的所有操作都是同步的&#xff0c;并且没有异步调用&#xff0c;则…...

adb目录笔记《adb更新、进入开发者模式,adb查询packages、adb开启应用,查询进程、强制删除进程》

1.sideload模式 在需要安卓没有root权限的时候&#xff0c;可以使用adb reboot sideload命令进入sideload模式&#xff0c;之后运行对应文件 adb reboot sideload adb sideload <root.zip> 2.packages包查询、运行、删除 在需要查看安卓中packages包的名称时&#xf…...

VS2022 C++ EasyX EGE 吃豆人升级版

我是可爱的C小盆友&#xff08;不要脸了&#xff09;&#xff0c;嘻嘻&#xff0c;等了这么久&#xff0c;吃豆人终于升级啦&#xff01; 更新日志&#xff1a; 1.修复奇奇怪怪的bug 2.把敌人AI增强了一&#xff08;hen&#xff09;点&#xff08;duo&#xff09; 3.加入了…...

计算机图形学 | 动画模拟

动画模拟 布料模拟 质点弹簧系统&#xff1a; 红色部分很弱地阻挡对折 Steep connection FEM:有限元方法 粒子系统 粒子系统本质上就是在定义个体和群体的关系。 动画帧率 VR游戏要不晕需要达到90fps Forward Kinematics Inverse Kinematics 只告诉末端p点&#xff0c;中间…...

B2.3 Arm 内存模型定义

B2.3 Arm 内存模型定义 Arm 内存模型引入了以下几种关系: 内在关系 :例如,内在数据/控制/顺序依赖关系和内在翻译之前的关系,这些是源自指令语义的硬件要求。 之后关系 :例如,之后的连贯性和 TLB 之后的关系,这些关系在特定执行中发生这种方式,但在不同的执行中可以以…...

(javaweb)SpringBootWeb案例(毕业设计)案例--部门管理

目录 1.准备工作 2.部门管理--查询功能 3.前后端联调 3.部门管理--新增功能 1.准备工作 mapper数据访问层相当于dao层 根据页面原型和需求分析出接口文档--前后端必须遵循这种规范 大部分情况下 接口文档由后端人员来编写 前后端进行交互基于restful风格接口 http的请求方式…...

PCL 采样一致性模型介绍

采样一致性可以简单高效的检测出一些具有数学表达式的目标模型。PCL中的sample consensus模块中不仅包含各种的采样一致性估计方法,也包含一些已经编写好的数学模型,下面主要介绍一下PCL中的采样一致性模型。 1. 二维圆模型 pcl::SampleConsensusModelCircle2D< PointT …...

地震勘探——干扰波识别、井中地震时距曲线特点

目录 干扰波识别反射波地震勘探的干扰波 井中地震时距曲线特点 干扰波识别 有效波&#xff1a;可以用来解决所提出的地质任务的波&#xff1b;干扰波&#xff1a;所有妨碍辨认、追踪有效波的其他波。 地震勘探中&#xff0c;有效波和干扰波是相对的。例如&#xff0c;在反射波…...

在鸿蒙HarmonyOS 5中实现抖音风格的点赞功能

下面我将详细介绍如何使用HarmonyOS SDK在HarmonyOS 5中实现类似抖音的点赞功能&#xff0c;包括动画效果、数据同步和交互优化。 1. 基础点赞功能实现 1.1 创建数据模型 // VideoModel.ets export class VideoModel {id: string "";title: string ""…...

Mac软件卸载指南,简单易懂!

刚和Adobe分手&#xff0c;它却总在Library里给你写"回忆录"&#xff1f;卸载的Final Cut Pro像电子幽灵般阴魂不散&#xff1f;总是会有残留文件&#xff0c;别慌&#xff01;这份Mac软件卸载指南&#xff0c;将用最硬核的方式教你"数字分手术"&#xff0…...

【HarmonyOS 5 开发速记】如何获取用户信息(头像/昵称/手机号)

1.获取 authorizationCode&#xff1a; 2.利用 authorizationCode 获取 accessToken&#xff1a;文档中心 3.获取手机&#xff1a;文档中心 4.获取昵称头像&#xff1a;文档中心 首先创建 request 若要获取手机号&#xff0c;scope必填 phone&#xff0c;permissions 必填 …...

【从零开始学习JVM | 第四篇】类加载器和双亲委派机制(高频面试题)

前言&#xff1a; 双亲委派机制对于面试这块来说非常重要&#xff0c;在实际开发中也是经常遇见需要打破双亲委派的需求&#xff0c;今天我们一起来探索一下什么是双亲委派机制&#xff0c;在此之前我们先介绍一下类的加载器。 目录 ​编辑 前言&#xff1a; 类加载器 1. …...

python爬虫——气象数据爬取

一、导入库与全局配置 python 运行 import json import datetime import time import requests from sqlalchemy import create_engine import csv import pandas as pd作用&#xff1a; 引入数据解析、网络请求、时间处理、数据库操作等所需库。requests&#xff1a;发送 …...

Ubuntu系统复制(U盘-电脑硬盘)

所需环境 电脑自带硬盘&#xff1a;1块 (1T) U盘1&#xff1a;Ubuntu系统引导盘&#xff08;用于“U盘2”复制到“电脑自带硬盘”&#xff09; U盘2&#xff1a;Ubuntu系统盘&#xff08;1T&#xff0c;用于被复制&#xff09; &#xff01;&#xff01;&#xff01;建议“电脑…...

API网关Kong的鉴权与限流:高并发场景下的核心实践

&#x1f525;「炎码工坊」技术弹药已装填&#xff01; 点击关注 → 解锁工业级干货【工具实测|项目避坑|源码燃烧指南】 引言 在微服务架构中&#xff0c;API网关承担着流量调度、安全防护和协议转换的核心职责。作为云原生时代的代表性网关&#xff0c;Kong凭借其插件化架构…...

[特殊字符] 手撸 Redis 互斥锁那些坑

&#x1f4d6; 手撸 Redis 互斥锁那些坑 最近搞业务遇到高并发下同一个 key 的互斥操作&#xff0c;想实现分布式环境下的互斥锁。于是私下顺手手撸了个基于 Redis 的简单互斥锁&#xff0c;也顺便跟 Redisson 的 RLock 机制对比了下&#xff0c;记录一波&#xff0c;别踩我踩过…...

TCP/IP 网络编程 | 服务端 客户端的封装

设计模式 文章目录 设计模式一、socket.h 接口&#xff08;interface&#xff09;二、socket.cpp 实现&#xff08;implementation&#xff09;三、server.cpp 使用封装&#xff08;main 函数&#xff09;四、client.cpp 使用封装&#xff08;main 函数&#xff09;五、退出方法…...