收集有关在程序中实例化哪些模板变体的信息
Collecting information on which template variants are being instantiated in a program
今天我了解到,当我们有一个带有静态成员变量的 C++ class 模板时,它的构造函数不会被调用(实际上甚至不会定义该成员),除非我们 "use it in a way that requires the definition of the static data member to exist".
这里很好地解释了这种现象:
C++ Static member initalization (template fun inside)
实际上,这意味着如果我们想要初始化(以及它的任何可能的副作用)发生,我们必须显式引用该静态成员的每个实例(从 class 模板之外)。
我一直在想办法解决这个问题。
我的动机是有一个现有的代码库,它使用 class 模板 Foo
的各种实例化(它有多个模板参数,但为了这个例子我简化了它)我会喜欢自动收集有关所有不同参数组合的信息。
我几乎不能等待所有这些 Foo
在程序执行期间被构建(这是一个很长的 运行ning 后台进程)所以我想我可以放一个静态的 Initializer<T>
在 Foo<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://github.com/djdeath/glib/blob/master/glib/gconstructor.h)。
不过,为了让它在这种情况下工作,我还不得不求助于这个非常奇怪的 hack:有一个全局变量模板 g_constructors__
,它在 (!) Foo
.
老实说,我真的很惊讶这个效果。我知道它是非标准的,但有人可以解释它是如何编译的吗?这仅仅是运气还是某种程度上 "well-formed" 至少就 Microsoft C++ 而言?
我知道我可以改用某种外部静态分析工具来做到这一点,但这与我想要的非常接近,主要优点是它都被纳入了被检查的程序。
如果我可以为每个 T 调用 Initializer(看起来我可以),提取类型信息很容易。我可以使用 boost typeindex 或我需要的任何其他东西对模板参数进行字符串化。全局计数器在这里仅用于查看是否正在创建 Initializer
个实例。
如果您愿意为您的对象添加一个额外变量的成本,这似乎可以满足您的要求,尽管这一切都非常复杂,我可能会遗漏一个案例:
#include <iostream>
static int& gInt() { // Global counter
static int i;
return i;
}
struct Initializer {
Initializer() { ++gInt(); }
};
template <class T>
struct Foo {
Foo() { // This never gets called and we want that
std::cout << "Constructed Foo!" << std::endl;
}
private:
static Initializer gint_incrementer;
void* p_ = &gint_incrementer; // force existence
};
template <typename T>
Initializer Foo<T>::gint_incrementer;
void unused() {
Foo<char> c;
Foo<char> c2;
Foo<double> d;
Foo<int> i;
Foo<float> f;
}
int main() {
std::cout << gInt() << std::endl; // prints 4
}
如果我们使用 schwartz counter 惯用语的变体,则无需黑客也无需 unused()
函数即可实现:
global_counter.hpp
#pragma once
struct GlobalCounter {
int next();
int value() const;
int value_ = 0;
struct init {
init();
~init();
};
};
extern GlobalCounter& globalCounter;
static GlobalCounter::init globalCounterInit;
global_counter.cpp
#include "global_counter.hpp"
#include <memory>
#include <type_traits>
static int globalCounterCount;
static std::aligned_storage_t <sizeof(GlobalCounter), alignof(GlobalCounter)> globalCounterStorage;
GlobalCounter& globalCounter = reinterpret_cast<GlobalCounter&>(globalCounterStorage);
GlobalCounter::init::init() {
if(globalCounterCount++ == 0) new (&globalCounter) GlobalCounter ();
}
GlobalCounter::init::~init() {
if (--globalCounterCount == 0) globalCounter.~GlobalCounter();
}
int GlobalCounter::next() {
return value_++;
}
int GlobalCounter::value() const {
return value_;
}
foo.hpp
#pragma once
#include "global_counter.hpp"
#include <iostream>
template <class T>
struct Foo {
Foo() { // This never gets called and we want that
std::cout << "Constructed Foo!" << std::endl;
}
static int ident;
};
template<class T>
int Foo<T>::ident;
template<class T>
struct EnableFoo
{
EnableFoo()
{
if (counter++ == 0)
Foo<T>::ident = globalCounter.next();
}
static int counter;
};
template<class T> int EnableFoo<T>::counter;
static EnableFoo<char> enableFooChar;
static EnableFoo<int> enableFooInt;
static EnableFoo<double> enableFooDouble;
static EnableFoo<float> enableFooFloat;
main.cpp
#include <iostream>
#include "foo.hpp"
int main() {
std::cout << globalCounter.value() << std::endl; // prints 4
return 0;
}
查看 schwartz 计数器:
https://en.wikibooks.org/wiki/More_C%2B%2B_Idioms/Nifty_Counter
好的,因为我的问题的解决方案只是部分解决方案,我将分享一个有效的代码片段,它实际上显示了我所追求的,现在我已经弄清楚了。
我们不需要任何特定于编译器的 hack,以下应该普遍适用:
#include <iostream>
#include <vector>
#include <string>
#include <mutex>
#include <boost/type_index.hpp>
static std::vector<std::string>& getInstantiations() {
static std::vector<std::string> instantiations;
return instantiations;
}
static std::mutex& getMutex() {
static std::mutex mut;
return mut;
}
template <class T>
struct Introspection {
Introspection() {
std::lock_guard<std::mutex> lock(getMutex());
getInstantiations().push_back(boost::typeindex::type_id<T>().pretty_name());
}
void forceExistence() {}
};
template <class... Ts>
struct Foo {
Foo() { // This never gets called and we want that
std::cout << "Constructed Foo!" << std::endl;
introspection.forceExistence();
}
private:
static Introspection<Foo<Ts...>> introspection;
};
template <class... Ts>
Introspection<Foo<Ts...>> Foo<Ts...>::introspection;
void unused() {
Foo<char> c;
Foo<char> c2;
Foo<double> d;
Foo<int, const int> i;
Foo<float, bool, long> f;
}
int main() {
for (auto& i : getInstantiations()) {
std::cout << i << std::endl;
}
/*
output:
Foo<char>
Foo<double>
Foo<int, int const>
Foo<float, bool, long>
*/
}
它看起来很傻,但考虑一个更大的项目,到处都是 Foo<...>
声明。是的,也许我可以只使用正则表达式搜索,但这样我就可以在检查程序为 运行 时实际处理收集到的信息。回应类型名称只是我们可以用它做的最简单的例子。
今天我了解到,当我们有一个带有静态成员变量的 C++ class 模板时,它的构造函数不会被调用(实际上甚至不会定义该成员),除非我们 "use it in a way that requires the definition of the static data member to exist".
这里很好地解释了这种现象: C++ Static member initalization (template fun inside)
实际上,这意味着如果我们想要初始化(以及它的任何可能的副作用)发生,我们必须显式引用该静态成员的每个实例(从 class 模板之外)。
我一直在想办法解决这个问题。
我的动机是有一个现有的代码库,它使用 class 模板 Foo
的各种实例化(它有多个模板参数,但为了这个例子我简化了它)我会喜欢自动收集有关所有不同参数组合的信息。
我几乎不能等待所有这些 Foo
在程序执行期间被构建(这是一个很长的 运行ning 后台进程)所以我想我可以放一个静态的 Initializer<T>
在 Foo<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://github.com/djdeath/glib/blob/master/glib/gconstructor.h)。
不过,为了让它在这种情况下工作,我还不得不求助于这个非常奇怪的 hack:有一个全局变量模板 g_constructors__
,它在 (!) Foo
.
老实说,我真的很惊讶这个效果。我知道它是非标准的,但有人可以解释它是如何编译的吗?这仅仅是运气还是某种程度上 "well-formed" 至少就 Microsoft C++ 而言?
我知道我可以改用某种外部静态分析工具来做到这一点,但这与我想要的非常接近,主要优点是它都被纳入了被检查的程序。
如果我可以为每个 T 调用 Initializer(看起来我可以),提取类型信息很容易。我可以使用 boost typeindex 或我需要的任何其他东西对模板参数进行字符串化。全局计数器在这里仅用于查看是否正在创建 Initializer
个实例。
如果您愿意为您的对象添加一个额外变量的成本,这似乎可以满足您的要求,尽管这一切都非常复杂,我可能会遗漏一个案例:
#include <iostream>
static int& gInt() { // Global counter
static int i;
return i;
}
struct Initializer {
Initializer() { ++gInt(); }
};
template <class T>
struct Foo {
Foo() { // This never gets called and we want that
std::cout << "Constructed Foo!" << std::endl;
}
private:
static Initializer gint_incrementer;
void* p_ = &gint_incrementer; // force existence
};
template <typename T>
Initializer Foo<T>::gint_incrementer;
void unused() {
Foo<char> c;
Foo<char> c2;
Foo<double> d;
Foo<int> i;
Foo<float> f;
}
int main() {
std::cout << gInt() << std::endl; // prints 4
}
如果我们使用 schwartz counter 惯用语的变体,则无需黑客也无需 unused()
函数即可实现:
global_counter.hpp
#pragma once
struct GlobalCounter {
int next();
int value() const;
int value_ = 0;
struct init {
init();
~init();
};
};
extern GlobalCounter& globalCounter;
static GlobalCounter::init globalCounterInit;
global_counter.cpp
#include "global_counter.hpp"
#include <memory>
#include <type_traits>
static int globalCounterCount;
static std::aligned_storage_t <sizeof(GlobalCounter), alignof(GlobalCounter)> globalCounterStorage;
GlobalCounter& globalCounter = reinterpret_cast<GlobalCounter&>(globalCounterStorage);
GlobalCounter::init::init() {
if(globalCounterCount++ == 0) new (&globalCounter) GlobalCounter ();
}
GlobalCounter::init::~init() {
if (--globalCounterCount == 0) globalCounter.~GlobalCounter();
}
int GlobalCounter::next() {
return value_++;
}
int GlobalCounter::value() const {
return value_;
}
foo.hpp
#pragma once
#include "global_counter.hpp"
#include <iostream>
template <class T>
struct Foo {
Foo() { // This never gets called and we want that
std::cout << "Constructed Foo!" << std::endl;
}
static int ident;
};
template<class T>
int Foo<T>::ident;
template<class T>
struct EnableFoo
{
EnableFoo()
{
if (counter++ == 0)
Foo<T>::ident = globalCounter.next();
}
static int counter;
};
template<class T> int EnableFoo<T>::counter;
static EnableFoo<char> enableFooChar;
static EnableFoo<int> enableFooInt;
static EnableFoo<double> enableFooDouble;
static EnableFoo<float> enableFooFloat;
main.cpp
#include <iostream>
#include "foo.hpp"
int main() {
std::cout << globalCounter.value() << std::endl; // prints 4
return 0;
}
查看 schwartz 计数器:
https://en.wikibooks.org/wiki/More_C%2B%2B_Idioms/Nifty_Counter
好的,因为我的问题的解决方案只是部分解决方案,我将分享一个有效的代码片段,它实际上显示了我所追求的,现在我已经弄清楚了。
我们不需要任何特定于编译器的 hack,以下应该普遍适用:
#include <iostream>
#include <vector>
#include <string>
#include <mutex>
#include <boost/type_index.hpp>
static std::vector<std::string>& getInstantiations() {
static std::vector<std::string> instantiations;
return instantiations;
}
static std::mutex& getMutex() {
static std::mutex mut;
return mut;
}
template <class T>
struct Introspection {
Introspection() {
std::lock_guard<std::mutex> lock(getMutex());
getInstantiations().push_back(boost::typeindex::type_id<T>().pretty_name());
}
void forceExistence() {}
};
template <class... Ts>
struct Foo {
Foo() { // This never gets called and we want that
std::cout << "Constructed Foo!" << std::endl;
introspection.forceExistence();
}
private:
static Introspection<Foo<Ts...>> introspection;
};
template <class... Ts>
Introspection<Foo<Ts...>> Foo<Ts...>::introspection;
void unused() {
Foo<char> c;
Foo<char> c2;
Foo<double> d;
Foo<int, const int> i;
Foo<float, bool, long> f;
}
int main() {
for (auto& i : getInstantiations()) {
std::cout << i << std::endl;
}
/*
output:
Foo<char>
Foo<double>
Foo<int, int const>
Foo<float, bool, long>
*/
}
它看起来很傻,但考虑一个更大的项目,到处都是 Foo<...>
声明。是的,也许我可以只使用正则表达式搜索,但这样我就可以在检查程序为 运行 时实际处理收集到的信息。回应类型名称只是我们可以用它做的最简单的例子。