现代C ++的发展趋势表明,在代码中最大程度地拒绝了宏。 但是有时没有宏,并且以它们特别丑陋的表现,就无法做,因为没有宏会更糟。 关于这个和故事。
如您所知,编译C和C ++的第一步是预处理器,它用纯文本替换宏和预处理器指令。
这使我们可以做一些奇怪的事情,例如:
在预处理器工作之后,这种误解将变成正确的代码:
std::string str = "look, I'm a string!" ;
当然,此可怕的标头不能包含在其他任何地方。 是的,由于我们将多次将此标头添加到同一个文件中,因此没有#pragma一次,也没有包含防护。
实际上,让我们编写一个更复杂的示例,该示例将在宏的帮助下执行不同的操作,同时防止随机出现的#include:
这仍然很丑陋,但是已经出现了某种魅力:将新元素添加到枚举类时,它将自动添加到重载的输出语句中。
在这里,您可以形式化此方法的应用领域:基于一个来源在不同位置生成代码的需求。
现在是X-Macro和Windows的悲惨故事。 有诸如Windows Performance Counters这样的系统,它使您可以将某些计数器发送到操作系统,以便其他应用程序可以选择它们。 例如,可以将Zabbix配置为收集和监视任何性能计数器。 这非常方便,并且您不需要通过返回/查询数据来重新构造轮子。
我真诚地认为,添加一个新计数器看起来像一个HANDLE计数器= AddCounter(“名称”)。 啊,我错了。
首先,您需要编写一个特殊的XML清单(
示例 ),或者使用Windows SDK中的ecmangen.exe程序生成它,但是由于
某种原因,该ecmangen
已从 Windows 10 SDK的新版本中
删除 。 接下来,您需要根据我们的XML清单使用
ctrpp实用程序生成代码和.rc文件。 仅使用
lodctr实用程序
将新计数器添加到系统本身,并在参数中包含XML清单。
什么是.rc文件?这是Microsoft的发明,与标准C ++不相关。 使用这些文件,您可以将资源(例如字符串\图标\图片等)嵌入exe \ dll中,然后使用特殊的Windows API进行提取。
Perfcounters使用这些.rc文件来本地化计数器名称,但不清楚为什么要本地化这些名称。
总结以上内容:要添加1个计数器,您需要:
- 更改XML清单
- 根据清单生成新的.c和.rc项目文件
- 编写一个新函数,将增加一个新计数器
- 编写一个将获取计数器值的新函数
总计:diff-e中有4-5个修改过的文件,以便使用一个计数器,并且不断遭受XML清单的影响,而XML清单是plus代码中的信息源。 这就是微软为我们提供的。
实际上,发明的解决方案看起来很吓人,但是在一个文件中恰好在一行中添加了一个新的计数器。 此外,由于仍然需要XML清单,尽管现在还不是主要的清单,但所有内容都是使用宏和不幸的是使用预构建脚本自动生成的。
我们的perfcounters_ctr.h看起来与上面的示例几乎相同:
#ifndef NV_PERFCOUNTER #error "You cannot do this!" #endif ... NV_PERFCOUNTER(copied_bytes) NV_PERFCOUNTER(copied_files) ... #undef NV_PERFCOUNTER
如我先前所写,添加计数器是通过使用lodctr.exe加载XML清单来完成的。 在我们的程序中,我们只能对其进行初始化和修改。
我们在生成的打开器中感兴趣的初始化片段如下所示:
#define COPIED_BYTES 0
总计:我们需要采用“计数器名称-递增索引”形式的对应关系,并且在编译阶段,必须知道计数器的数量并从计数器索引中收集初始化数组。 这是X宏进行救援的地方。
将计数器名称与其上升索引进行匹配非常简单。
下面的代码将变成一个枚举类,其内部索引从0开始,然后递增1。 用手添加最后一个元素,我们立即找出有多少总计数器:
enum class counter_enum : int { #define NV_PERFCOUNTER(ctr) ctr, #include "perfcounters_ctr.h" total_counters };
而且,根据我们的枚举,我们需要初始化计数器:
static constexpr int counter_count = static_cast<int>(counter_enum::total_counters); const PERF_COUNTERSET_INFO counterset_info{ ... counter_count, ... }; struct { PERF_COUNTERSET_INFO set; PERF_COUNTER_INFO counters[counter_count]; } counterset { counterset_info, {
结果是,新计数器的初始化现在需要1行,并且不需要其他文件中的其他更改(以前,每次重新生成仅在初始化时更改了3条代码)。
让我们添加一个方便的API来增加计数器。 精神上的东西:
#define NV_PERFCOUNTER(ctr) \ inline void ctr##_tick(size_t diff = 1) { } #include "perfcounters_ctr.h" #define NV_PERFCOUNTER(ctr) \ inline size_t ctr##_get() { } #include "perfcounter_ctr.h"
预处理器将为我们生成漂亮的getter / setter,我们可以立即在代码中使用它们,例如:
inline void copied_bytes_tick(size_t diff = 1); inline size_t copied_bytes_get();
但是,我们仍然有2件令人遗憾的事情:XML清单和.rc文件(可惜,这是必要的)。
我们非常简单-一个预构建脚本,该脚本使用定义计数器的宏读取原始文件,解析“ NV_COUNTER(“和”)”之间的内容,并基于此生成两个.gitignore文件,以便不要乱扔差异。
它是 :基于XML清单的特殊软件生成的编码代码。 每次添加/删除计数器,项目中都会发生很多更改。
现在:预处理程序和预构建脚本生成所有计数器,XML清单和.rc文件。 diff-e中恰好一行用于添加/删除计数器。 感谢预处理器帮助解决了这个问题,在这种特殊情况下,显示出的好处多于伤害。