C++ 编译时程序范围内的唯一编号

标签 c++ templates c-preprocessor

我想出了一个问题的解决方案,但我不确定它是始终有效还是只在我的编译器上有效。首先,问题:我注意到在很多情况下,希望有一个模板类在每次使用时都被重新实例化,即使在给定相同类型的情况下也是如此(比如你的模板类有静态成员被初始化为函数调用有一些重要的副作用——你希望每次使用模板时都产生这种副作用)。执行此操作的简单方法是为您的模板提供一个额外的整数参数:

template<class T, class U, int uniqueify>
class foo
{
...
}

但现在您必须手动确保每次使用 foo 时都将不同的值传递给唯一化。天真的解决方案是像这样使用 __LINE__:

#define MY_MACRO_IMPL(line) foo<line>
#define MY_MACRO MY_MACRO_IMPL(__LINE__)

虽然这个解决方案有一个问题 -- __LINE__ 为每个翻译单元重置。因此,如果两个翻译单元在同一行上使用模板,则模板只会被实例化一次。这似乎不太可能,但想象一下,如果它真的发生了,调试编译器错误会有多困难。类似地,您可以尝试以某种方式使用 __DATE__ 作为参数,但这只有秒精度,并且是编译开始的时间,而不是编译到达该行的时间,因此如果您使用的是 make 的并行版本有两个具有相同 __DATE__ 的翻译单元是相当合理的。

另一个解决方案是一些编译器有一个特殊的非标准宏,__COUNTER__ 从 0 开始,每次使用它时递增。但它遇到了同样的问题——它会在每次调用预处理器时重置,因此它会在每个翻译单元重置。

另一种解决方案是将 __FILE____LINE__ 一起使用:

#define MY_MACRO_IMPL(file, line) foo<T, U, file, line>
#define MY_MACRO MY_MACRO_IMPL(T, U, __FILE__, __LINE__)

但是你不能根据标准将字 rune 字作为模板参数传递,因为它们没有外部链接。

即使这确实有效,__FILE__ 是否包含文件的绝对路径或仅文件本身的名称在标准中没有定义,所以如果你有两个相同的命名文件不同的文件夹,这仍然可能会中断。所以这是我的解决方案:

#ifndef toast_unique_id_hpp_INCLUDED
#define toast_unique_id_hpp_INCLUDED

namespace {
namespace toast {
namespace detail {

template<int i>
struct translation_unit_unique {
    static int globally_unique_var;
};

template<int i>
int translation_unit_unique<i>::globally_unique_var;

}
}
}

#define TOAST_UNIQUE_ID_IMPL(line) &toast::detail::translation_unit_unique<line>::globally_unique_var
#define TOAST_UNIQUE_ID TOAST_UNIQUE_ID_IMPL(__LINE__)

#endif

如果没有使用示例,为什么这行得通还不是很清楚,但首先要进行概述。我的主要见解是,每次创建全局变量或静态成员变量时,您都会以该变量地址的形式创建程序范围内的唯一编号。所以这为我们提供了一个在编译时可用的唯一编号。 __LINE__ 确保我们不会在同一翻译单元内发生冲突,并且外部匿名命名空间确保变量在翻译单元之间是不同的实例(因此获得不同的地址)。

示例用法:

template<int* unique_id>
struct special_var
{
    static int value;
}

template<int* unique_id>
int special_var<unique_id>::value = someSideEffect();

#define MY_MACRO_IMPL(unique_id) special_var<unique_id>
#define MY_MACRO MY_MACRO_IMPL(TOAST_UNIQUE_ID)

foo.cpp 变成:

#include <toast/unique_id.hpp>

...

typedef MY_MACRO unique_var;
typedef MY_MACRO unique_var2;
unique_var::value = 3;
unique_var2::value = 4;
std::cout << unique_var::value << unique_var2::value;

尽管是相同的模板,并且用户没有提供不同的参数,unique_varunique_var2 是不同的。

我最担心的是匿名命名空间中变量的地址 in 在编译时实际上可用。从技术上讲,匿名命名空间就像声明内部链接,模板参数不能有内部链接。但是标准所说的处理匿名命名空间的方式就像变量被声明为具有程序范围内唯一名称的命名空间的一部分,这意味着从技术上讲它确实具有外部链接,即使我们通常不要这样想。所以我认为标准站在我这边,但我不确定。

我不知道我是否已经尽了最大的努力来解释为什么这会很有用,但为了这次讨论,我发誓它是有用的;)

最佳答案

这种技术一般来说并不安全,原因有二。

  1. __LINE__ 可以在同一翻译单元的两个不同行上相等,通过 #line 指令,或者(更常见的)通过在同一翻译单元上使用多个头文件中的行号。

  2. 如果在内联函数或头文件中的模板定义中使用 TOAST_UNIQUE_ID 或从中派生的任何内容,将会违反 ODR。

就是说,如果您从不在头文件中使用它,并且不在您的主源文件中使用 #line,并且每行只使用一次宏,这似乎是安全的。 (您可以通过从 __LINE__ 切换到 __COUNTER__ 来移除最后一个限制。)

关于C++ 编译时程序范围内的唯一编号,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/1222922/

相关文章:

c++ - 链接和编译 pcl

c - C预处理器中的strlen?

c++ - 依赖#defines 的最佳实践?

检查宏内部的宏定义

c++ - 如何使用成员函数创建 packaged_task?

c++ - 使用 std::vector<unsigned char> 的内容初始化结构体

c# - 为什么我可以从基于 C#/C++ 中该类型的模板化/泛型类派生

java - 未加载 Spring Boot 模板文件

JavaH 无法为 JNI 类创建 C 头文件

c++ - 使用没有模板的模板化类