1. Class Reflection
After analyzing simple classes and structures, we can further analyze the reflection of ordinary classes. There are generally several situations for reflecting a class: 1. Creating and matching objects by class name 2. Reflecting class member variables 3. Reflecting class member functions 4. Handling class inheritance relationships. Here, we will only deal with the basic parts and temporarily ignore complex applications such as inheritance.
2. Implementation Mechanism
The class reflection mechanism mainly focuses on how to obtain class types, member functions, and variables for expansion. Related complex applications will be analyzed in detail later. There are several ways to implement class reflection: 1. Using basic STL-related metaprogramming libraries, which can generally only perform checks but cannot directly call class objects and members. 2. Using macros and templates (including metaprogramming), which is more complex but may be more comprehensive in application. 3. Using open-source frameworks or libraries, which are essentially encapsulated reflection calls by others, but styles may vary widely.
Here, we do not strictly distinguish between dynamic or static reflection but analyze based on the implementation mechanism. As for which type this implementation belongs to or whether it is a combination of both, it can be judged by the reader.
3. Implementation
We have previously implemented member reflection similar to POD types, so we will skip those here. We will also analyze the use of the Traits STL library to determine class and member characteristics in a separate chapter, as some individual usages have already been analyzed in detail. Therefore, this article mainly focuses on reflecting the creation of objects and ordinary member function calls.
4. Example Analysis
Let’s look at a preliminary implementation: 1. Basic instance object management class
#pragma once
#include <string>
#include <iostream>
class Ins
{
public:
void SetName(const std::string& cName)
{
className = cName;
}
void getName() const
{
std::cout << className << std::endl;
}
public:
void GetFunc(const std::string& FuncName);
public:
std::string className;
};
typedef Ins* (*createIns)(void);
class ClassFunc
{
public:
ClassFunc(const std::string& name, uint64_t Func) : funcName(name), pFunc(Func) {}
const std::string& Name() { return funcName; }
const uint64_t Func() { return pFunc; }
private:
std::string funcName;
uint64_t pFunc;
};
#include "ins.h"
#include <functional>
#include "singleton.h"
#include "factory.h"
void Ins::GetFunc(const std::string& funcName)
{
ClassFunc* ClassFunc = Singleton<Factory>::Instance()->GetMemFunc(className, funcName);
if (ClassFunc == nullptr) {
std::cerr << "get func err!" << std::endl;
return;
}
typedef std::function<void(Ins*)> CFunc;
(*reinterpret_cast<CFunc*>(ClassFunc->Func()))(this);
}
2. A singleton class
#pragma once
#include <mutex>
template<typename T>
class Singleton
{
public:
Singleton() = default;
~Singleton() = default;
Singleton(const Singleton& other) = delete;
Singleton& operator=(const Singleton& other) = delete;
public:
static T* Instance()
{
static T* t = new T();
return t;
}
};
Note: Safe since C++0x.
3. Registering a factory
#pragma once
#include <map>
#include <string>
#include <vector>
#include "singleton.h"
#include "ins.h"
class Factory
{
public:
Factory() = default;
virtual ~Factory() = default;
public:
void ClassReg(const std::string& className, createIns func);
Ins* GetObject(const std::string& className);
void RegMemFunc(const std::string& className, ClassFunc* func);
ClassFunc* GetMemFunc(const std::string& className, const std::string& funcName);
private:
std::map<std::string, createIns> mapClassName;
std::map<std::string, std::vector<ClassFunc*>> mapClassFunc;
};
#include "factory.h"
void Factory::ClassReg(const std::string& className, createIns func)
{
this->mapClassName.emplace(className,func);
}
Ins* Factory::GetObject(const std::string& className)
{
if (this->mapClassName.count(className) > 0) {
return this->mapClassName[className]();
}
return nullptr;
}
void Factory::RegMemFunc(const std::string& className, ClassFunc* func)
{
mapClassFunc[className].emplace_back(func);
}
ClassFunc* Factory::GetMemFunc(const std::string& className, const std::string& funcName)
{
if (this->mapClassFunc.count(className) > 0)
{
for (auto func : mapClassFunc[className])
{
if (func->Name()==funcName)
{
return func;
}
}
}
return nullptr;
}
4. Registering and testing classes
#include <iostream>
#include <mutex>
#include <map>
#include <vector>
#include <functional>
#include "singleton.h"
#include "factory.h"
class RegClass
{
public:
RegClass(const std::string& className, createIns func)
{
Singleton<Factory>::Instance()->ClassReg(className, func);
}
public:
RegClass(const std::string& className, const std::string& funcName, uint64_t func)
{
auto factory = Singleton<Factory>::Instance();
factory->RegMemFunc(className, new ClassFunc(funcName, func));
}
};
#define REG_CLASS(class_name) \
Ins* createIns##class_name() \
{ \
Ins* o = new class_name(); \
o->SetName(#class_name); \
return o; \
} \
RegClass RegClass##class_name(#class_name, createIns##class_name)
#define REG_CLASS_FUNC(class_name, func_name) \
std::function<void(class_name*)> class_name##func_name = &class_name::func_name; \
RegClass RegClass##class_name##func_name(#class_name, #func_name, (uint64_t)&(class_name##func_name))
class Ex0 :public Ins
{
};
REG_CLASS(Ex0);
class Ex1 : public Ins
{
};
REG_CLASS(Ex1);
void testClass()
{
auto factory = Singleton<Factory>::Instance();
auto i0 = factory->GetObject("Ex0");
i0->getName();
auto i1 = factory->GetObject("Ex1");
i1->getName();
}
////////////////////////////////////////////////////////////////////////////////////////////
class Ex2 :public Ins
{
public:
void Test()
{
std::cout << "Ex2 Test!" << std::endl;
}
};
REG_CLASS(Ex2);
REG_CLASS_FUNC(Ex2, Test);
class Ex3 : public Ins
{
public:
void Test()
{
std::cout << "Ex3 Test!" << std::endl;
}
};
REG_CLASS(Ex3);
REG_CLASS_FUNC(Ex3, Test);
void testFunc()
{
auto factory = Singleton<Factory>::Instance();
Ins* i2 = factory->GetObject("Ex2");
Ins* i3 = factory->GetObject("Ex3");
i2->GetFunc("Test");
i3->GetFunc("Test");
}
int main()
{
testClass();
testFunc();
return 0;
}
For class reflection, the creation of object instances is done by registering a function pointer to create instances; the processing of member functions involves saving their addresses in a map and calling them when needed. One aspect that may be difficult to understand is the use of integers to replace class member pointers; this is not an issue since pointers are just addresses, and an address is a variable type pointing to a location. The above code is quite clear and does not require further explanation; it mainly borrows from the class reflection implementation method in QT. There is still significant room for improvement in this code, but it is only intended to demonstrate the process of implementing reflection, so it aims to be simple and clear. For example, the function to register class-generated objects could use template functions for more flexibility. Further optimizations will be made when time allows.
5. Conclusion
Whether reflection in C++ is a redundant feature is hard to say. However, one saying can be introduced: “Development is the hard truth”; as long as there is continuous development, there will be opportunities. Empty theoretical discussions that stagnate will certainly lead to no future. As for what reflection will become in future C++ standards, we can only wait and see.