C++ 编译时程序范围内的唯一编号
我已经提出了一个问题的解决方案,但我不确定它是否总是有效或仅在我的编译器上有效。 首先,问题是:我注意到在很多情况下,希望有一个模板类,即使给定相同的类型,每次使用它时也会重新实例化(假设您的模板类具有初始化为函数调用的静态成员)有一些重要的副作用——并且您希望每次使用模板时都会产生这种副作用)。 执行此操作的简单方法是为模板提供一个额外的整数参数:
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__ 作为参数,但这只有秒精度,并且是编译开始的时间,而不是到达该行的时间,因此如果您使用的是 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__)
但是您不能根据标准将 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
是不同的。
我最担心的是匿名命名空间中变量的地址在编译时实际上可用。 从技术上讲,匿名命名空间就像声明内部链接,模板参数不能有内部链接。 但是标准规定处理匿名命名空间的方式就像变量被声明为具有程序范围唯一名称的命名空间的一部分一样,这意味着从技术上讲,它确实具有外部链接,即使我们通常不这么认为。 所以我认为标准在我这边,但我不确定。
我不知道我是否已经很好地解释了为什么这会有用,但为了这次讨论,我发誓,它是有用的;)
I've come up with a solution to a problem but I'm not sure if it'll always work or just on my compiler. First, the problem: I've noticed in a number of situations it's desirable to have a template class that gets re-instantiated each time it's used even when given the same types (say your template class has static members that are initialized to function calls that have some important side effect -- and you want this side effect to be done every time the template is used). The easy way to do this is to give your template an extra integer parameter:
template<class T, class U, int uniqueify>
class foo
{
...
}
But now you have to manually make sure that every time you use foo you pass it a different value for uniqueify. The naive solution is to use __LINE__
like this:
#define MY_MACRO_IMPL(line) foo<line>
#define MY_MACRO MY_MACRO_IMPL(__LINE__)
This solution has an issue though -- __LINE__
gets reset for each translation unit. So if two translation units use the template on the same line, the template only gets instantiated once. That may seem unlikely, but imagine how difficult to debug the compiler error it would be if it did happen. Similarly you could try using __DATE__
as a parameter somehow, but that only has seconds precision and it's the time when compiling started, not when it reaches that line, so if you're using a parallel version of make it's rather plausible to have two translation units with the same __DATE__
.
Another solution is that some compilers have a special non-standard macro, __COUNTER__
that starts at 0 and increments every time you use it. But it suffers from the same problem -- it gets reset for each invocation of the preprocessor, so it gets reset each translation unit.
Yet another solution, is to use __FILE__
and __LINE__
together:
#define MY_MACRO_IMPL(file, line) foo<T, U, file, line>
#define MY_MACRO MY_MACRO_IMPL(T, U, __FILE__, __LINE__)
But you can't pass char literals as template parameters according to the standard because they don't have external linkage.
Even if this did work, whether __FILE__
contains the absolute path to the file or just the name of the file itself isn't defined in the standard, so if you had two identical named files in different folders, this could still break. So here is my solution:
#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
Why this works isn't really clear without a usage example, but first an overview. The key insight I had was to see that every time you make a global variable or a static member variable, you're creating a program wide unique number in the form of the address of that variable. So this gives us a unique number that's available at compile time. __LINE__
makes sure we won't get clashes within the same translation unit, and the outer anonymous namespace makes sure the variables are different instances (and thus get differing addresses) across translation units.
Example usage:
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)
And foo.cpp becomes:
#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;
Despite being the same template, and the user providing no differentiating parameters, unique_var
and unique_var2
are distinct.
I'm mostly worried about the address in of the variable in the anonymous namespace actually being available at compile time. Technically, an anonymous namespace is like declaring internal linkage, and template parameters can't have internal linkage. But the way the standard says to treat anonymous namespaces is just like the variable was declared as part of a namespace with a program-wide unique name, which means that technically it does have external linkage, even though we don't usually think of it as such. So I think the standard is on my side, but I'm not sure.
I don't know if I've done the best job of explaining why this would be useful, but for the sake of this discussion, it is, I swear ;)
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
这种技术一般来说并不安全,原因有二。
__LINE__
在同一翻译单元中的两个不同行上可以相等,可以通过#line
指令,或者(更常见)通过在相同的行号上使用多个头文件。如果您在内联函数或头文件中的模板定义中使用
TOAST_UNIQUE_ID
或从它派生的任何内容,您将违反 ODR。也就是说,如果您从不在头文件中使用它,并且不在主源文件中使用
#line
,并且每行仅使用该宏一次,那么它似乎是安全的。 (您可以通过从__LINE__
切换到__COUNTER__
来删除最后一个限制。)This technique is not safe in general, for two reasons.
__LINE__
can be equal on two different lines in the same translation unit, either through#line
directives, or (more commonly) through use on the same line number in multiple header files.You will have ODR violations if you use
TOAST_UNIQUE_ID
or anything derived from it within in inline function or template definition in a header file.That said, if you never use this in header files, and don't use
#line
in your main source file, and only use the macro once per line, it seems safe. (You can remove that last restriction by switching from__LINE__
to__COUNTER__
.)这应该是安全的 - 但更简单的方法是仅使用FILE。 另外,int 在 64 位平台上是不够的。 使用 intptr_t:
此外,请记住,在宏或模板中使用时,这会崩溃。
此外,具有带有副作用的静态值的模板是一个坏主意 - 请记住,在调用 main() 之前,副作用以任意顺序发生 - 并且将初始化副作用隐藏在随机函数中对于可维护性来说并不是很好。
This should be safe - but simpler way would be to just use FILE. Also, an int isn't enough on a 64-bit platform. Use an intptr_t:
Further, keep in mind that this will break down when used within a macro or template.
Also, templates with static values with side effects are a bad idea - remember that the side effects occur in arbitrary order, before main() is called - and burying initialization side effects in random functions is not very good for maintainability.