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

Jos*_*vin 5 c++ templates c-preprocessor

我想出了一个问题的解决方案,但我不确定它是否总是有效或仅适用于我的编译器。首先,问题:我注意到在许多情况下,即使给定相同的类型,也希望每次使用时都重新实例化模板类(假设您的模板类具有初始化为函数调用的静态成员)有一些重要的副作用——并且您希望每次使用模板时都会产生这种副作用)。最简单的方法是给你的模板一个额外的整数参数:

template<class T, class U, int uniqueify>
class foo
{
...
}
Run Code Online (Sandbox Code Playgroud)

但是现在您必须手动确保每次使用 foo 时都会为 uniqueify 传递一个不同的值。天真的解决方案是这样使用__LINE__

#define MY_MACRO_IMPL(line) foo<line>
#define MY_MACRO MY_MACRO_IMPL(__LINE__)
Run Code Online (Sandbox Code Playgroud)

不过这个解决方案有一个问题——__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__)
Run Code Online (Sandbox Code Playgroud)

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

即使这确实有效,__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
Run Code Online (Sandbox Code Playgroud)

如果没有使用示例,为什么这会起作用并不是很清楚,但首先是概述。我的主要见解是看到每次创建全局变量或静态成员变量时,都会以该变量地址的形式创建程序范围内的唯一编号。因此,这为我们提供了一个在编译时可用的唯一编号。__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)
Run Code Online (Sandbox Code Playgroud)

而 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;
Run Code Online (Sandbox Code Playgroud)

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

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

我不知道我是否在解释为什么这会有用方面做得最好,但为了这次讨论,我发誓;)

bdo*_*lan 1

这应该是安全的 - 但更简单的方法是仅使用FILE。另外,int 在 64 位平台上是不够的。使用 intptr_t:

template<const char *file, int line>
class unique_value {
  static char dummy;
  unique_value() { }
public:
  static intptr_t value() { return (intptr_t)&dummy; }
};

#define UNIQUE_VALUE (unique_value<__FILE__, __LINE__>::value())
Run Code Online (Sandbox Code Playgroud)

此外,请记住,在宏或模板中使用时,这会崩溃。

此外,具有带有副作用的静态值的模板是一个坏主意 - 请记住,在调用 main() 之前,副作用以任意顺序发生 - 并且将初始化副作用隐藏在随机函数中对于可维护性来说并不是很好。