【发布时间】:2017-08-30 21:47:03
【问题描述】:
今天我了解到,当我们有一个带有静态成员变量的 C++ 类模板时,除非我们“以需要定义要存在的静态数据成员”。
这个现象在这里得到了很好的解释: C++ Static member initalization (template fun inside)
在实践中,这意味着如果我们想要初始化(以及它的任何可能的副作用)发生,我们必须显式地引用该静态成员的每个实例化(从类模板外部)。
我一直在想办法解决这个问题。
我的动机是有一个现有的代码库,它使用类模板Foo 的各种实例化(它有多个模板参数,但为了示例我简化了),我想自动收集有关所有不同的参数组合。
我实际上不能等待在程序执行期间构建所有这些Foos(这是一个长时间运行的后台进程),所以我认为我可以在Foo<T> 中放置一个静态Initializer<T> 并让它提取所需的在程序启动后立即为每个不同的Foo 类型键入信息。
在这种情况下,为了让初始化程序首先运行而必须枚举Initializer<T> Foot<T>::init 的所有实例显然违背了目的。我必须去看看(在整个项目中)这些类型是什么,而这正是我想要实现的自动化。
我注意到,如果我用持有本地静态Initializer 实例的静态方法替换静态成员变量,我可以更轻松地强制生成Initializer<T> 定义。我只需要获取一个指向该方法的指针(例如在Foo 的构造函数中)。
最后一步是在程序启动后调用这个静态方法。在 g++/clang 的情况下,使用 __attribute__((constructor)) 就像一个魅力。
不过,我还必须处理 MSVC++,这就是我想出的:
#include <iostream>
#if defined(_MSC_VER) && !defined(__clang__)
#define MSVC_CONSTRUCTOR_HACK
#define ATTRIBUTE_CONSTRUCTOR
#else
#define ATTRIBUTE_CONSTRUCTOR __attribute__((constructor))
#endif
static int& gInt() { // Global counter
static int i;
return i;
}
template <class T>
struct Initializer {
// If it works, this gets called for each Foo<T> declaration
Initializer() {
gInt()++;
}
};
#ifdef MSVC_CONSTRUCTOR_HACK
__pragma(section(".CRT$XCU", read))
template <class T> // This will hold pointers to Foo<T>::getInit
static void(*g_constructors__)(void);
#endif
template <class T>
struct Foo {
ATTRIBUTE_CONSTRUCTOR // Empty in case of MSVC
static void getInit() {
static Initializer<T> init;
}
#ifdef MSVC_CONSTRUCTOR_HACK
template <> // Why is this allowed?!
__declspec(allocate(".CRT$XCU")) static void(*g_constructors__<T>)(void) = getInit;
#endif
Foo() { // This never gets called and we want that
std::cout << "Constructed Foo!" << std::endl;
(void)&getInit; // This triggers instantiation and definition of Initializer<T>
}
};
void unused() {
Foo<char> c;
Foo<double> d;
Foo<int> i;
Foo<float> f;
}
int main() {
std::cout << gInt() << std::endl; // prints 4
return 0;
}
它依赖于将函数指针放入可执行文件的 .CRT 部分(https://stackoverflow.com/a/2390626/6846474、https://github.com/djdeath/glib/blob/master/glib/gconstructor.h)。
不过,为了让它在这种情况下工作,我还不得不求助于这个非常奇怪的技巧:有一个全局变量模板g_constructors__,它在内部明确专门化(!)Foo。
说实话,我真的很惊讶这个作品。我知道它是非标准的,但有人可以解释它是如何编译的吗?这只是运气,还是至少就 Microsoft C++ 而言,它是否“格式良好”?
我知道我可以改用某种外部静态分析工具来做到这一点,但这与我想要的非常接近,主要优势在于它全部整合到被检查的程序中。
如果我可以为每个 T 调用 Initializer(看起来我可以),那么提取类型信息就很容易了。我可以使用 boost typeindex 或我需要的任何其他东西对模板参数进行字符串化。此处使用全局计数器仅用于查看是否正在创建 Initializer 实例。
【问题讨论】:
-
糟糕,格式化问题。只是缺少一个花括号。我修复了它,所以它现在应该可以工作了。它至少在 Visual Studio 2017 中编译。
-
看起来您真正的问题是如何收集有关在您的程序中实例化哪些模板变体的信息。我不知道确切的解决方案,但它可能与解析目标代码和/或使用一些专用工具来内省 AST(可能是基于 clang 的东西?)有关。
-
@VTT 好吧,我考虑了各种方法,包括对现有代码执行额外静态分析的外部程序(或 clang 插件),但我宁愿将类型提取合并到程序中本身。在这里我真的很好奇这段代码。不过,也欢迎任何有关如何以不同方式进行操作的建议。
标签: c++ templates initialization static-members