I have seen a lot of misinformation about C++ not having out-of-the-box reflection and if you do want full reflection, you must use other languages, install libraries or give up.
But... It seems like c++ supports reflection just fine. I can relatively simply add a system that prints out even a private variables' name, value and type out in c++11. It can also be used to check if a variable of certain name and type exists on a class. Am I misunderstanding what reflections means in this context, or is stackoverflow consensus just incorrect?
Code example:
Factory.h, basic factory with object registry
#pragma once
#include <cstring> // strcmp
#include <functional>
#include <string>
#include <unordered_map>
#include "Object.h"
// Allows Object to be spawnable through SpawnObject.
// Registers the class with the factory's registry, and connects that to a templated SpawnObject function
#define SPAWNABLE(CLASSNAME) class CLASSNAME; static bool CLASSNAME##Registered = (ObjectFactory::GetInstance().GetObjectRegistry()[#CLASSNAME] = &ObjectFactory::SpawnObject<CLASSNAME>, true)
// Spawns a class of selected name
#define DoSpawn ObjectFactory::GetInstance().SpawnObjectByName
// Find a class of selected name
#define FindObject ObjectFactory::GetInstance().FindObjectByName
// Singleton object factory
class ObjectFactory
{
public:
std::unordered_multimap<std::string, Object*> Objects;
// Gets instance of the simpleton
static ObjectFactory& GetInstance()
{
static ObjectFactory Instance;
return Instance;
}
// A templated function to spawn any registered Object
template <typename TObject>
static Object* SpawnObject()
{
return new TObject();
}
// A factory function that spawns an object of the specified class name
Object* SpawnObjectByName(const std::string& ClassName, const char* EditorName = "")
{
auto it = GetObjectRegistry().find(ClassName);
if (it != GetObjectRegistry().end())
{
Object* obj = it->second();
Objects.insert(std::make_pair(EditorName, obj));
obj->Name = EditorName;
obj->RegisterVariables();
return obj;
}
return nullptr;
}
std::unordered_map<std::string, std::function<Object* ()>>& GetObjectRegistry() // Returns the Registry of class names
{
static std::unordered_map<std::string, std::function<Object* ()>> Registry;
return Registry;
}
Object* FindObjectByName(std::string Name)
{
auto it = Objects.find(Name);
if (it == Objects.end())
{
return nullptr;
}
return it->second;
}
private:
std::unordered_map<std::string, std::function<Object* ()>> ObjectRegistry; // Registry that maps class names to factory functions
};
Object.h class, the base for reflection
#pragma once
#include <map>
#include <string>
#include <typeinfo>
#include <vector>
// Holds variable type, value and name so it can be accessed
struct RegistryEntry
{
const std::type_info* type;
void* value;
const char* name;
};
#define EditorVariableRegistry ObjectVariableRegistry::GetInstance().VariableRegistry
class ObjectVariableRegistry
{
public:
static ObjectVariableRegistry& GetInstance()
{
static ObjectVariableRegistry Instance;
return Instance;
}
std::multimap<void*, RegistryEntry> VariableRegistry;
private:
ObjectVariableRegistry() {}
ObjectVariableRegistry(const ObjectVariableRegistry&) = delete;
};
class Object
{
protected:
std::vector<RegistryEntry> EditorVariables; // Container of all variables changable in Editor
public:
#define SERIALIZE(var) EditorVariables.push_back(RegistryEntry{&typeid(var), &(var), #var}), true;
void RegisterVariables() // Registers all editor variables into a global list. Does not work properly if placed in .cpp file
{
SERIALIZE(Name);
for (auto& entry : EditorVariables) // Loops through the EditorVariables and add them to the global VariableRegistry
{
EditorVariableRegistry.emplace(this, entry);
}
}
std::string Name = "";
};
Foo.h. A simple class which inherits from Object, meaning it now has out-of-the box support for reflection.
#pragma once
#include "Factory.h"
SPAWNABLE(Foo);
class Foo : public Object
{
public:
Foo()
{
SERIALIZE(Bar);
}
private:
std::string Bar = "private variable";
};
Finally: Main.cpp. Note how we don't #include Foo.h
anywhere in the program, and we can still inspect (and modify) Foo variables as long as we #include "Factory.h"
.
#include <iostream>
#include "Factory.h"
#include "Foo.h"
void PrintAllVariables()
{
printf("Registry contents: (%zi) \n", EditorVariableRegistry.size()) ;
for (const auto& var : EditorVariableRegistry)
{
if (Object* obj = static_cast<Object*>(var.first))
{
std::cout << " Owning class name: " << obj->Name << " Owning class instance address: " << var.first << std::endl;
}
std::cout << " Variable name: " << var.second.name << " Variable address: " << var.second.value << std::endl;
std::cout << " Type: " << var.second.type->name() << std::endl;
std::cout << " Value: ";
// Check the type of the variable
if (*var.second.type == typeid(std::string))
{
std::cout << *static_cast<std::string*>(var.second.value);
}
}
}
int main()
{
DoSpawn("Foo", "Dude");
PrintAllVariables();
return 0;
}
Output:
Registry contents: (2)
Owning class name: Dude Owning class instance address: 0x561afd16cf80
Variable name: Bar Variable address: 0x561afd16cfb8
Type: NSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEE
Value: private variable Owning class name: Dude Owning class instance address: 0x561afd16cf80
Variable name: Name Variable address: 0x561afd16cf98
Type: NSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEE
Value: Dude
Aucun commentaire:
Enregistrer un commentaire