C ++编译时程序范围内的唯一编号 [英] C++ compile time program wide unique numbers

查看:90
本文介绍了C ++编译时程序范围内的唯一编号的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想出一个解决问题的方法,但是不确定它是否将一直有效或仅在我的编译器上有效。首先是问题:我注意到在很多情况下,希望有一个模板类,即使给定相同的类型,也要在每次使用时重新实例化(例如,您的模板类具有静态成员,这些成员被初始化为函数调用)具有一些重要的副作用-并且您希望每次使用模板时都可以完成此副作用)。这样做的简单方法是为模板提供一个额外的整数参数:

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
{
...
}

但是现在您必须手动确保每次使用foo时,都要为其传递一个不同的值来唯一化。天真的解决方案是像这样使用 __ LINE __

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__)

此解决方案有问题- __ LINE __ 将为每个翻译单元重置。因此,如果两个翻译单元在同一行上使用模板,则该模板仅实例化一次。这似乎不太可能,但请想象一下,如果确实发生调试错误将非常困难。同样,您可以尝试使用 __ DATE __ 作为参数,但这仅具有秒精度,这是开始编译的时间,而不是到达该行的时间,所以如果使用make的并行版本,使两个翻译单元具有相同的 __ DATE __ 似乎很合理。

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__.

另一种解决方案是一些编译器有一个特殊的非标准宏 __ COUNTER __ ,该宏从0开始,每次使用时递增。但是它也遇到了同样的问题-每次调用预处理器都会将其重置,因此会重置每个转换单元。

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.

另一种解决方案是使用 __ FILE __ __ LINE __ 一起:

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__)

但是您可以

即使按照标准,也不能按照标准将char文字作为模板参数传递。

But you can't pass char literals as template parameters according to the standard because they don't have external linkage.

即使这样做确实可行, __ FILE __ 包含文件的绝对路径,或者只是文件本身的名称未在标准中定义,因此,如果在不同的文件夹中有两个相同的命名文件,则该文件仍然可能损坏。所以这是我的解决方案:

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

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

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.

示例用法:

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变为:

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;

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

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 ;)

推荐答案

这种技术通常并不安全,有两个原因。

This technique is not safe in general, for two reasons.


  1. __ LINE __ 可以在同一转换单元中的两个不同行上相等,可以通过 #line 指令,也可以(通常)通过在多个头文件中的同一行号上使用。

  1. __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.

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

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.

也就是说,如果您从不在头文件中使用它,并且不要在主源文件中使用 #line ,并且每行仅使用一次宏,这似乎很安全。 (您可以通过从 __ LINE __ 切换到 __ COUNTER __ 来删除最后一个限制。)

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__.)

这篇关于C ++编译时程序范围内的唯一编号的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆