【发布时间】:2021-03-11 16:50:09
【问题描述】:
上下文
我目前正在尝试实现一个 C++ 项目设置,它允许我在一个依赖树中拥有两个不同版本的库。为此,我需要对包含和命名空间中的版本号进行编码。据我所知,这只能通过使用宏来完成。所以我的第一种方法是简单地将这个宏的定义硬编码到每个源文件中,比如#define myLib myLib_1_0_0。这有一个问题,我需要在提交文件之前知道文件的版本。这很难看,因为我希望版本来自 VCS(在我的情况下是 Git),而不是在每个文件中重复。此外,它还需要为每个新版本更改每个源文件,这会破坏更改日志。
问题
问题是,我无法想出一种机制/破解方法来获取每个文件的宏。使用编译器-D 选项,我只能为整个编译单元设置宏,这意味着它将为所有包含的头文件定义为相同的值。这不是我想要的。 如何为单个文件定义预处理器宏?
不要犹豫,提出丑陋或老套的解决方案。目前这只是一个实验,我想看看这是否可能。
hacky 解决方案的想法
C++ 提供了为单个文件定义的__FILE__ 宏。我希望我可以检查这个宏以包含另一个定义我需要的宏的文件。在这里我被卡住了,因为我无法使用 __FILE__ 宏来创建不同的包含路径,因为它已经被字符串化了。
进一步的背景和动机
我想要的是找到一个实验性解决方案,我可以将两个不同版本的库链接到消费者库并同时使用它们。我完成这项工作的方法是将版本号编码为目标名称、包含路径和命名空间,因此编译器和构建系统会认为这两个版本是完全不同的依赖项。
这是我目前想出的:
包含帮助宏的文件VersionUtils.h。它是强制包含在库和消费者的所有编译单元中的。
// File VersionUtils.h
#define stringify(x) #x
#define VERSIONED_INCLUDE2(a, b) stringify( a ## / ## b)
#define VERSIONED_INCLUDE(a, b) VERSIONED_INCLUDE2(a, b)
#define VERSIONED_NAMESPACE(ns) namespace ns
1.0.0 版本的库文件和一些虚拟代码。
// File MyLib.h
#define myLib myLib_1_0_0
VERSIONED_NAMESPACE(myLib)
{
int myFunction();
}
// File MyLib.cpp
#define myLib myLib_1_0_0
VERSIONED_INCLUDE(myLib, MyLib.h)
VERSIONED_NAMESPACE(myLib)
{
int myFunction()
{
return 42;
}
}
2.0.0 版本的库文件,带有一些伪代码。
// File MyLib.h
#define myLib myLib_2_0_0
VERSIONED_NAMESPACE(myLib)
{
int myFunction();
}
// File MyLib.cpp
#define myLib myLib_2_0_0
VERSIONED_INCLUDE(myLib, MyLib.h)
VERSIONED_NAMESPACE(myLib)
{
int myFunction()
{
return 1337;
}
}
MyExe 可执行文件中的消费者main.cpp 文件。
// File main.cpp
#include <iostream>
#define myLib1 myLib_1_0_0
#define myLib2 myLib_2_0_0
VERSIONED_INCLUDE(myLib1, MyLib.h)
VERSIONED_INCLUDE(myLib2, MyLib.h)
int main()
{
std::cout << myLib1::myFunction() << "\n"; // prints 42
std::cout << myLib2::myFunction() << "\n"; // prints 1337
}
为了使这个工作,库的头文件不能使用由头文件之外的宏定义的版本号。当定义被硬编码到头文件中时它可以工作,但由于上述问题,这不是一个可以容忍的解决方案。
还请注意,这只是对更复杂的实际情况的简化,其中同一库的两个版本位于大型依赖关系树的深处,消费者可能无法控制所使用的 myLib 版本。
【问题讨论】:
-
您可能需要考虑从 CVS 升级。你能解释一下为什么在每个头文件的开头
#define一个宏(但在它包含任何其他头文件之后)然后#undef在最后对你不起作用吗? -
我将添加有关我的问题的更多详细信息。
-
如果库有两个不同的版本,为什么需要逐个文件指定版本,而不是项目级别
-DmyLib=2?无关,在我的 C++ 项目中,我将有namespace myLib { namespace v1 { ... } namespace v2 { ... }}来组织版本,这允许一些增量迁移路径。特别是如果一个函数改变了它的返回类型或 noexcept 标签或参数。 -
如果我理解正确的话,你需要和你的版本控制系统沟通,这显然是系统特定的。一些系统,至少有插件,能够在签入时在文件中插入版本字符串。虽然该工具通常用于编写 cmets,但它可能用于定义。或者,您可以使用手工制作的签入脚本来更新仅包含版本号的文件,该文件可以由签入脚本读取,也可以用于初始化静态常量等。
-
这就是脚本的用途。我有一个构建脚本,它在构建时将 git commit 哈希注入到源代码中。很像 CVS 这样的史前 SCM。这是我的自定义自动构建脚本。 C++ 并不能解决世界上的所有问题。有时需要使用其他方法,例如脚本、XML 生成的代码等……在使用普通 Makefile 和脚本时,很容易破解自定义构建脚本。但是,如果一个人被 GUI IDE 束缚,带有漂亮的菜单和按钮,那么你能做的就是:看看漂亮的菜单和按钮。根本没有脚本功能。
标签: c++ macros preprocessor