最近学习c++ 的反射机制的实现方式, 想要达到仅通过使用类名的字符串就能方便地拿到该类实例的方法。要做到上述的目标,显而易见的,我们需要使用某种机制记录这种字符串保存的类名和类实例获取方法(函数)的映射关系。 我们首先想到的就是用一个std::map数据结构去存取这种映射关系。 这个map 存贮在一个工厂类中(这里和工厂模式有些不同,但大体上是用来直接获取实例的途径。和工厂类类似,因此采用工厂类这个叫法,后面不再重述), 工厂类提供注册映射关系和通过得到实例的方法。 再者如果每次新增一个类, 都需要先在工厂类中完成注册才能使用。那在应用代码中加这些注册的代码,一来会使得耦合变强,二来每次手动注册加重了应用层的工作量。 因此可以巧妙的设计一个宏来帮我们完成这块。这样再每次设计完一个需要实例化的类后,可以立刻调用宏注册, 而在应用层面的代码中手动注册,让代码便于维护。
//工厂类头文件class_factory.h
#ifndef _CLASS_FACTORY_H
#define _CLASS_FACTORY_H
#include <iostream>
#include <map>
#include "static_utils.h"
// 存放的是Register中的 instance函数
typedef void * (*fun_ptr) ();
typedef std::map<const char *, fun_ptr> create_obj_map;
class ClassFactory {
public:
static void * get_instance(const char *);
static void register_class(const char *, fun_ptr);
};
#endif</pre><p></p><pre>
//工厂类定义 class_factory.cpp
#include "class_factory.h"
void * ClassFactory::get_instance(const char* class_name) {
if (_Static::_static<0, create_obj_map>().find(class_name) == _Static::_static<0, create_obj_map>().end()) {
std::cerr << "not found\n";
return NULL;
}
std::cout << "found\n";
return _Static::_static<0, create_obj_map>()[class_name]();
}
void ClassFactory::register_class(const char* class_name, fun_ptr fp) {
_Static::_static<0, create_obj_map>().insert(std::make_pair(class_name,fp));
std::cout << "insert after" << class_name <<std::endl;
}//register.h 宏和辅助注册类
#ifndef _REGISTER_H
#define _REGISTER_H
#include "class_factory.h"
class Register {
public:
Register(const char *, fun_ptr);
};
#define REGISTER(class_name) \
class Register##class_name { \
public: \
static void * instance() { \
return new class_name; \
} \
private: \
static const Register _static_register; \
}; \
const Register Register##class_name::_static_register(#class_name, Register##class_name::instance);
#endif
//辅助注册类的实现
Register::Register(const char* class_name, fun_ptr fp) {
ClassFactory::register_class(class_name, fp);
}//static_utils.h静态辅助类
#ifndef _STATIC_UTIL_H
#define _STATIC_UTIL_H
class _Static {
public:
template<int N, class T>
static T & _static() {
//initial once
static T _instance;
return _instance;
}
};
#endif// 要注册的类,简单举列foo.h
#ifndef _FOO_H
#define _FOO_H
#include <string>
#include "register.h"
class Foo {
public:
Foo() {
_id = 1;
_name = "ok";
}
public:
int _id;
std::string _name;
};
class Foo1 {
public:
Foo1() {
_id = 1;
_name = "foo1";
}
public:
int _id;
std::string _name;
};
REGISTER(Foo);
REGISTER(Foo1);
#endif
//main函数应用
#include <iostream>
#include "register.h"
#include "foo.h"
//REGISTER(Foo);
//REGISTER(Foo1);
int main() {
Foo * foo= static_cast<Foo*>(ClassFactory::get_instance("Foo")) ;
std::cout << foo->_name;
Foo1 * foo1= static_cast<Foo1*>(ClassFactory::get_instance("Foo1")) ;
std::cout << foo1->_name;
return 0;
}以上就实现了利用类名字符串获取类实例的方法。
当然在实现的过程中也遇到了一些问题, 心得总结如下 其中,一 是上面代码我体会的一些核心思路和思想;二是在实现方式上遇到的问题及解决方法
1. 自动注册,采用了反射机制
1.1 一个工厂记录了所有的类名和得到class obj的方法指针
1.1.1 工厂类中需要一个注册方法,记录class_name和 得到class obj的函数
1.1.2 工厂类中需要一个根据class_name 获取class obj的函数,用来给应用方调用
1.1.3 以上两条都是静态方法
1.2 需要一个辅助类,该类在构造函数中调用注册
1.3 定义一个宏, 方便在写代码的时候用来完成注册
1.3.1 宏的本质是一个类, 类里有个静态函数用来获取注册类的obj, 所以这个函数也就是1.1要的方法指针指向的东东
1.3.2 宏定义的类中还需要一个静态成员变量, 该变量是一个辅助类,这样静态变量初始化时调用辅助类的构造函数,就完成注册过程
1.3.3 需要用传给辅助类的是类名和这个类(1.3.1)的静态函数名
1.3.4 静态变量记得在类外初始化,这里也需要定义在宏里
1.4使用的时候, eg 宏REGISTER(类名)注册后,可以直接使用工厂类的1.1.2获取类的实例
2. 静态变量初始化捣的鬼—工厂类中1.1如用静态map记录,会出core
2.1 由于程序中存在两个静态, 1.3.2中的静态变量初始化的时候, 会调用2说的静态map,此时map没有被初始化,所以core
2.1.1 通俗的说就是 1.3.2 静态变量依赖 2中的map ,因此有需要2 中变量先被初始化
2.1.2 不同环境下有些情况下可能不出core,原因是分散在不同文件.cpp和.obj中的静态变量初始化顺序不定,所以不出core未必没有风险
2.2 改进方法1. 将所有涉及静态变量的涉及类放到一个.cpp中, 将被依赖的初始化变量放在前就可以了,这样2 的map就可以不要了
2.2 改进方法2. 上面方法不适合复杂场景因此做如下改造,解决的核心在于保证初始化顺序,或者说确保用的时候,静态变量一定存在
2.2.1 被依赖的静态变量使用一个自定义的类在此类的静态函数中定义并返回需要类型的静态变量
2.2.1.1 此方法的核心是static定义的静态变量,仅初始化一次
2.2.1.2 函数的返回值是一个静态变量的引用, 因此可以作为左值使用
2.2.2 在所有用到改静态变量的地方(2 map) 都调用2.2.1中定义的静态函数
2.2.2.1 因为2.2.1.1的性质, 其实就实现了一个单例模式,这样保证在使用时,如果不存在就会去初始化使用
2.2.2.2 因为2.2.1.2的性质,保证该变量可作为左值进行赋值操作
作者水平有限, 仅写了一些自己对这种反射机制实现的理解, 如有不对的地方, 欢迎指正。
ps. 此文绝大部分实现和心得为原创,转载请注明出处。
C++ 类注册机制与静态变量初始化问题解析
本文探讨了在C++中利用反射机制实现类注册的方法,包括使用工厂类存储类名与实例化函数的映射,以及通过宏简化注册过程。在实现过程中遇到静态变量初始化导致的问题,如不同静态变量初始化顺序不确定可能导致核心错误。文章提出了两种解决方案,一种是将所有静态变量集中在一个.cpp文件中,另一种是通过自定义类的静态函数确保初始化顺序。
1844





