我可以有模板实例化副作用吗?
Can I have template instantiation side-effects?
所以,让我们从执行我想要的操作的代码开始,但我想教它一个新技巧(如果可能的话)。
#include <string>
#include <iostream>
class A {}; class B {}; class C {}; class D {};
template<typename T> struct traits { };
template<> struct traits<A> { static std::string code() {return "class A";}};
template<> struct traits<B> { static std::string code() {return "class B";}};
template<> struct traits<C> { static std::string code() {return "class C";}};
template<> struct traits<D> { static std::string code() {return "class D";}};
std::string RegisterClass(const std::string & c){
std::cout << "Registering " << c << '\n';
return c;
}
template<typename T> void printMe() {
static std::string t = RegisterClass(traits<T>::code());
std::cout << "Printing " << traits<T>::code() << '\n';
}
int main(void)
{
printMe<B>();
printMe<B>();
printMe<C>();
return 0;
}
它的输出正如人们所期望的那样——"registration" 只发生一次,并且只针对模板实例化的那些类型:
Registering class B
Printing class B
Printing class B
Registering class C
Printing class C
我想要的是在第一次调用[=13]之前"register"自己只有类 =] 功能。所以输出看起来像这样:
Registering class B
Registering class C
Printing class B
Printing class B
Printing class C
从表面上看,这似乎是可能的。编译器 "knows" 哪些类型用于实例化。如果我能够将该信息存储在一些全局或静态的东西中,那么我会在 main()
的开头处理它。
但到目前为止,我所有试图颠覆编译器的尝试都以失败告终。这让我怀疑这是故意的。所以'Can I have template instantiation side-effects? '他问,期待答案'no'?
编辑: 对不起,我真的没能把那句话表达清楚。在上面的示例中,我不需要 A,B,C and D
的注册——我只需要 B and C
的注册,我希望编译器以某种方式自己解决这个问题。
您可以通过创建一个助手 class 来做到这一点,它的构造函数负责注册 classes。然后在外部范围内创建助手 class 的实例。
#include <string>
#include <iostream>
class A {}; class B {}; class C {}; class D {};
template<typename T> struct traits { };
template<> struct traits<A> { static std::string code() {return "class A";}};
template<> struct traits<B> { static std::string code() {return "class B";}};
template<> struct traits<C> { static std::string code() {return "class C";}};
template<> struct traits<D> { static std::string code() {return "class D";}};
template<typename T>
void registerClass(){
std::cout << "Registering " << traits<T>::code() << '\n';
}
template<typename T> void printMe() {
std::cout << "Printing " << traits<T>::code() << '\n';
}
struct Init
{
Init();
};
Init::Init()
{
registerClass<A>();
registerClass<B>();
registerClass<C>();
registerClass<D>();
}
static Init init;
int main(void)
{
printMe<B>();
printMe<B>();
printMe<C>();
return 0;
}
添加静态成员变量并实例化。
namespace {
template<typename T>
class registrar {
public:
registrar() {
std::string t = RegisterClass(traits<T>::code());
}
};
}
template <class type> struct traits {
virtual std::string code() = 0; // override this
private:
static registrar m_registrar;
};
registrar traits<A>::m_registrar;
通过这种方式,traits<T>
的任何给定 T
因此会产生在运行时开始时调用 registrar<T>::registrar()
的副作用。
是的,使用在命名空间范围内实例化的静态成员变量。例如:
template<typename T>
struct RegisterClass
{
static RegisterClass instance;
RegisterClass()
{
std::cout << "Registering " << traits<T>::code() << '\n';
}
static RegisterClass& doRegister() { return instance; }
};
template<typename T>
RegisterClass<T> RegisterClass<T>::instance;
template<typename T> void printMe() {
RegisterClass<T>::doRegister();
std::cout << "Printing " << traits<T>::code() << '\n';
}
使用静态成员函数的唯一原因 doRegister
是为了避免来自未使用变量的警告。
另请参阅 How does the scheme below garantees there'll be just one definition for the objects cin, cout, ...? 了解与全局对象和初始化顺序相关的一些问题。
这可行,但不是一个正确的答案,因为我不确定为什么静态变量如此奇怪。
#include <iostream>
using namespace std;
template <typename T>
class Registrar {
public:
Registrar();
};
template <typename D>
struct SelfRegistrar {
static Registrar<D> d_;
SelfRegistrar(){
cout << &d_ << endl;
}
};
//for some reason I don't understand need some reference to d, opening another question...
class A : SelfRegistrar<A> { static void* foo() {return &d_;} };
class B : SelfRegistrar<B> { static void* foo() {return &d_;}};
class C : SelfRegistrar<C> { static void* foo() {return &d_;}};
class D : SelfRegistrar<D> { static void* foo() {return &d_;}};
template<typename T> struct traits { };
template<> struct traits<A> { static std::string code() {return "class A";}};
template<> struct traits<B> { static std::string code() {return "class B";}};
template<> struct traits<C> { static std::string code() {return "class C";}};
template<> struct traits<D> { static std::string code() {return "class D";}};
template <typename D>
Registrar<D> SelfRegistrar<D>::d_{};
std::string RegisterClass(const std::string & c){
std::cout << "Registering " << c << '\n';
return c;
}
template<typename T> void printMe() {
std::cout << "Printing " << traits<T>::code() << '\n';
}
template <typename D>
Registrar<D>::Registrar()
{
std::string c = traits<D>::code();
std::cout << "Registering " << c << '\n';
}
int main() {
// your code goes here
printMe<B>();
printMe<B>();
printMe<C>();
return 0;
}
有时从基础 class 派生比使用嵌入式或额外的助手 classes 更方便。根据您的需要,这种方法有时可以使事情变得更 simpler/cleaner 并且更可重用。
例如:
#include <string>
#include <iostream>
#include <typeinfo>
// Forward declaration for "traits" wrapper class. If you wanted to, you
// could to change the self_registering template to not use a wrapper class,
// default a wrapper but allow other specializations, etc.
template<typename T> struct traits;
template<typename wrappedClass>
struct self_registering {
self_registering() {};
virtual ~self_registering() {};
static bool isRegistered() { return registered; }
private:
typedef traits<wrappedClass> WrappedClass_;
static bool doRegistration() {
std::cout << "Default-registering " << WrappedClass_().code() << '\n';
return true;
};
static const bool registered;
};
template<typename T>
const bool self_registering<T>::registered
= self_registering<T>::WrappedClass_().doRegistration();
// our traits wrapper class...
template<typename T>
struct traits : self_registering<T> {
static std::string code() { return typeid(T).name(); };
};
// Well, that's pretty much it. Time to use it:
// a few classes we're going to (maybe) want self-registered...
class A {}; class B {}; class C {}; class D {};
class E {}; class F {}; class G {}; class H {};
// provide custom registration for class H:
template<> struct traits<H> : self_registering<H> {
static std::string code() { return "class H"; }
bool doRegistration() {
std::cout << "Private-registering " << code() << "\n";
// do something here like H::register()...
return true;
};
};
template<typename T>
void printMe() {
static bool isRegistered = traits<T>::isRegistered();
(void)isRegistered; // shut gcc up about unused variable
std::cout << "Printing " << traits<T>::code() << '\n';
}
int main(void)
{
printMe<B>();
printMe<B>();
printMe<C>();
printMe<H>();
return 0;
}
使用纯基于模板的自注册方法,一个缺点是如果您的所有 class 成员函数都是 static
,您将在某些时候需要以某种方式强制注册(如其他答案所示 - 例如 RegisterClass() 等)。
上面的代码通过简单地引用printMe()
中的静态成员变量registered
的值来实现这一点,强制编译器初始化它(它通过调用registration()
来完成) .该值分配给 static bool
,因此在启动时每个 class 只应注册一次。
虽然不能保证跨翻译单元的注册顺序,但它在 C++11 下应该是线程安全的(并且,如果您的代码在初始化静态对象之前没有进入多线程,则 C 之前++11)。如前所述,缺点是 registered
成员变量必须在您的代码中 某处 引用。
Compiled/tested 使用 VS2012 作为:
C:\tmp>cl /nologo /EHsc /W4 so-misc.cpp
so-misc.cpp
C:\tmp>so-misc
Default-registering class B
Default-registering class C
Private-registering class H
Printing class B
Printing class B
Printing class C
Printing class H
和 gcc 4.7.2 为:
$ gcc -std=c++11 -lstdc++ -Wall -pedantic so-misc.cpp
$ ./a.out | c++filt -t
Default-registering B
Default-registering C
Private-registering class H
Printing B
Printing B
Printing C
Printing class H
显然,您还可以做其他事情(进一步模板化 self-registering
使包装器 class 比 traits
更通用,一些清理等),但是这可能对你有用。
所以,让我们从执行我想要的操作的代码开始,但我想教它一个新技巧(如果可能的话)。
#include <string>
#include <iostream>
class A {}; class B {}; class C {}; class D {};
template<typename T> struct traits { };
template<> struct traits<A> { static std::string code() {return "class A";}};
template<> struct traits<B> { static std::string code() {return "class B";}};
template<> struct traits<C> { static std::string code() {return "class C";}};
template<> struct traits<D> { static std::string code() {return "class D";}};
std::string RegisterClass(const std::string & c){
std::cout << "Registering " << c << '\n';
return c;
}
template<typename T> void printMe() {
static std::string t = RegisterClass(traits<T>::code());
std::cout << "Printing " << traits<T>::code() << '\n';
}
int main(void)
{
printMe<B>();
printMe<B>();
printMe<C>();
return 0;
}
它的输出正如人们所期望的那样——"registration" 只发生一次,并且只针对模板实例化的那些类型:
Registering class B
Printing class B
Printing class B
Registering class C
Printing class C
我想要的是在第一次调用[=13]之前"register"自己只有类 =] 功能。所以输出看起来像这样:
Registering class B
Registering class C
Printing class B
Printing class B
Printing class C
从表面上看,这似乎是可能的。编译器 "knows" 哪些类型用于实例化。如果我能够将该信息存储在一些全局或静态的东西中,那么我会在 main()
的开头处理它。
但到目前为止,我所有试图颠覆编译器的尝试都以失败告终。这让我怀疑这是故意的。所以'Can I have template instantiation side-effects? '他问,期待答案'no'?
编辑: 对不起,我真的没能把那句话表达清楚。在上面的示例中,我不需要 A,B,C and D
的注册——我只需要 B and C
的注册,我希望编译器以某种方式自己解决这个问题。
您可以通过创建一个助手 class 来做到这一点,它的构造函数负责注册 classes。然后在外部范围内创建助手 class 的实例。
#include <string>
#include <iostream>
class A {}; class B {}; class C {}; class D {};
template<typename T> struct traits { };
template<> struct traits<A> { static std::string code() {return "class A";}};
template<> struct traits<B> { static std::string code() {return "class B";}};
template<> struct traits<C> { static std::string code() {return "class C";}};
template<> struct traits<D> { static std::string code() {return "class D";}};
template<typename T>
void registerClass(){
std::cout << "Registering " << traits<T>::code() << '\n';
}
template<typename T> void printMe() {
std::cout << "Printing " << traits<T>::code() << '\n';
}
struct Init
{
Init();
};
Init::Init()
{
registerClass<A>();
registerClass<B>();
registerClass<C>();
registerClass<D>();
}
static Init init;
int main(void)
{
printMe<B>();
printMe<B>();
printMe<C>();
return 0;
}
添加静态成员变量并实例化。
namespace {
template<typename T>
class registrar {
public:
registrar() {
std::string t = RegisterClass(traits<T>::code());
}
};
}
template <class type> struct traits {
virtual std::string code() = 0; // override this
private:
static registrar m_registrar;
};
registrar traits<A>::m_registrar;
通过这种方式,traits<T>
的任何给定 T
因此会产生在运行时开始时调用 registrar<T>::registrar()
的副作用。
是的,使用在命名空间范围内实例化的静态成员变量。例如:
template<typename T>
struct RegisterClass
{
static RegisterClass instance;
RegisterClass()
{
std::cout << "Registering " << traits<T>::code() << '\n';
}
static RegisterClass& doRegister() { return instance; }
};
template<typename T>
RegisterClass<T> RegisterClass<T>::instance;
template<typename T> void printMe() {
RegisterClass<T>::doRegister();
std::cout << "Printing " << traits<T>::code() << '\n';
}
使用静态成员函数的唯一原因 doRegister
是为了避免来自未使用变量的警告。
另请参阅 How does the scheme below garantees there'll be just one definition for the objects cin, cout, ...? 了解与全局对象和初始化顺序相关的一些问题。
这可行,但不是一个正确的答案,因为我不确定为什么静态变量如此奇怪。
#include <iostream>
using namespace std;
template <typename T>
class Registrar {
public:
Registrar();
};
template <typename D>
struct SelfRegistrar {
static Registrar<D> d_;
SelfRegistrar(){
cout << &d_ << endl;
}
};
//for some reason I don't understand need some reference to d, opening another question...
class A : SelfRegistrar<A> { static void* foo() {return &d_;} };
class B : SelfRegistrar<B> { static void* foo() {return &d_;}};
class C : SelfRegistrar<C> { static void* foo() {return &d_;}};
class D : SelfRegistrar<D> { static void* foo() {return &d_;}};
template<typename T> struct traits { };
template<> struct traits<A> { static std::string code() {return "class A";}};
template<> struct traits<B> { static std::string code() {return "class B";}};
template<> struct traits<C> { static std::string code() {return "class C";}};
template<> struct traits<D> { static std::string code() {return "class D";}};
template <typename D>
Registrar<D> SelfRegistrar<D>::d_{};
std::string RegisterClass(const std::string & c){
std::cout << "Registering " << c << '\n';
return c;
}
template<typename T> void printMe() {
std::cout << "Printing " << traits<T>::code() << '\n';
}
template <typename D>
Registrar<D>::Registrar()
{
std::string c = traits<D>::code();
std::cout << "Registering " << c << '\n';
}
int main() {
// your code goes here
printMe<B>();
printMe<B>();
printMe<C>();
return 0;
}
有时从基础 class 派生比使用嵌入式或额外的助手 classes 更方便。根据您的需要,这种方法有时可以使事情变得更 simpler/cleaner 并且更可重用。
例如:
#include <string>
#include <iostream>
#include <typeinfo>
// Forward declaration for "traits" wrapper class. If you wanted to, you
// could to change the self_registering template to not use a wrapper class,
// default a wrapper but allow other specializations, etc.
template<typename T> struct traits;
template<typename wrappedClass>
struct self_registering {
self_registering() {};
virtual ~self_registering() {};
static bool isRegistered() { return registered; }
private:
typedef traits<wrappedClass> WrappedClass_;
static bool doRegistration() {
std::cout << "Default-registering " << WrappedClass_().code() << '\n';
return true;
};
static const bool registered;
};
template<typename T>
const bool self_registering<T>::registered
= self_registering<T>::WrappedClass_().doRegistration();
// our traits wrapper class...
template<typename T>
struct traits : self_registering<T> {
static std::string code() { return typeid(T).name(); };
};
// Well, that's pretty much it. Time to use it:
// a few classes we're going to (maybe) want self-registered...
class A {}; class B {}; class C {}; class D {};
class E {}; class F {}; class G {}; class H {};
// provide custom registration for class H:
template<> struct traits<H> : self_registering<H> {
static std::string code() { return "class H"; }
bool doRegistration() {
std::cout << "Private-registering " << code() << "\n";
// do something here like H::register()...
return true;
};
};
template<typename T>
void printMe() {
static bool isRegistered = traits<T>::isRegistered();
(void)isRegistered; // shut gcc up about unused variable
std::cout << "Printing " << traits<T>::code() << '\n';
}
int main(void)
{
printMe<B>();
printMe<B>();
printMe<C>();
printMe<H>();
return 0;
}
使用纯基于模板的自注册方法,一个缺点是如果您的所有 class 成员函数都是 static
,您将在某些时候需要以某种方式强制注册(如其他答案所示 - 例如 RegisterClass() 等)。
上面的代码通过简单地引用printMe()
中的静态成员变量registered
的值来实现这一点,强制编译器初始化它(它通过调用registration()
来完成) .该值分配给 static bool
,因此在启动时每个 class 只应注册一次。
虽然不能保证跨翻译单元的注册顺序,但它在 C++11 下应该是线程安全的(并且,如果您的代码在初始化静态对象之前没有进入多线程,则 C 之前++11)。如前所述,缺点是 registered
成员变量必须在您的代码中 某处 引用。
Compiled/tested 使用 VS2012 作为:
C:\tmp>cl /nologo /EHsc /W4 so-misc.cpp
so-misc.cpp
C:\tmp>so-misc
Default-registering class B
Default-registering class C
Private-registering class H
Printing class B
Printing class B
Printing class C
Printing class H
和 gcc 4.7.2 为:
$ gcc -std=c++11 -lstdc++ -Wall -pedantic so-misc.cpp
$ ./a.out | c++filt -t
Default-registering B
Default-registering C
Private-registering class H
Printing B
Printing B
Printing C
Printing class H
显然,您还可以做其他事情(进一步模板化 self-registering
使包装器 class 比 traits
更通用,一些清理等),但是这可能对你有用。