我想出了一个问题的解决方案,但我不确定它是否总能工作或只是在我的编译器上.首先,问题是:我注意到在很多情况下,有一个模板类,每次使用它时都会被重新实例化,即使给定相同的类型(比如你的模板类有静态成员初始化为函数调用)这有一些重要的副作用 – 你希望每次使用模板时都能完成这种副作用.执行此操作的简单方法是为模板提供额外的整数参数:
template<class T, class U, int uniqueify>
class foo
{
...
}
但是现在你必须手动确保每次使用foo时都会为uniqueify传递一个不同的值.天真的解决方案是像这样使用__LINE__:
#define MY_MACRO_IMPL(line) foo<line>
#define MY_MACRO MY_MACRO_IMPL(__LINE__)
但是这个解决方案有一个问题 – 每个翻译单元都会重置__LINE__.因此,如果两个翻译单元在同一行上使用该模板,则该模板仅实例化一次.这看起来似乎不太可能,但想象一下,如果确实发生了调试编译错误会有多么困难.类似地,您可以尝试以某种方式使用__DATE__作为参数,但是它只有秒精度,而且是编译开始的时间,而不是当它到达该行时,所以如果你使用的是并行版本,那么有两个翻译是相当合理的具有相同__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__)
但是您不能根据标准将char文字作为模板参数传递,因为它们没有外部链接.
即使这确实有效,__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_var和unique_var2是不同的.
我最担心的是匿名命名空间中变量的地址实际上在编译时可用.从技术上讲,匿名命名空间就像声明内部链接一样,模板参数也不能具有内部链接.但是标准对待匿名命名空间的方式就像变量被声明为具有程序范围唯一名称的命名空间的一部分,这意味着从技术上讲它确实具有外部链接,即使我们通常不会想到它因此.所以我认为标准就在我身边,但我不确定.
我不知道我是否已经做了最好的解释为什么这会有用,但为了这个讨论,我发誓;)
最佳答案 这应该是安全的 – 但更简单的方法是只使用FILE.此外,在64位平台上,int是不够的.使用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())
此外,请记住,在宏或模板中使用时会出现故障.
此外,具有副作用的静态值的模板是一个坏主意 – 请记住,在调用main()之前,副作用以任意顺序发生 – 并且在随机函数中隐藏初始化副作用对于可维护性不是很好.