How can I add reflection to a C++ application?

2018-12-31 02:33发布

I'd like to be able to introspect a C++ class for its name, contents (i.e. members and their types) etc. I'm talking native C++ here, not managed C++, which has reflection. I realise C++ supplies some limited information using RTTI. Which additional libraries (or other techniques) could supply this information?

30条回答
永恒的永恒
2楼-- · 2018-12-31 02:57

What are you trying to do with reflection?
You can use the Boost type traits and typeof libraries as a limited form of compile-time reflection. That is, you can inspect and modify the basic properties of a type passed to a template.

查看更多
千与千寻千般痛.
3楼-- · 2018-12-31 02:59

Root Reflex project has support for this.

See https://root.cern.ch/how/how-use-reflex

查看更多
几人难应
4楼-- · 2018-12-31 03:00

The information does exist - but not in the format you need, and only if you export your classes. This works in Windows, I don't know about other platforms. Using the storage-class specifiers as in, for example:

class __declspec(export) MyClass
{
public:
    void Foo(float x);
}

This makes the compiler build the class definition data into the DLL/Exe. But it's not in a format that you can readily use for reflection.

At my company we built a library that interprets this metadata, and allows you to reflect a class without inserting extra macros etc. into the class itself. It allows functions to be called as follows:

MyClass *instance_ptr=new MyClass;
GetClass("MyClass")->GetFunction("Foo")->Invoke(instance_ptr,1.331);

This effectively does:

instance_ptr->Foo(1.331);

The Invoke(this_pointer,...) function has variable arguments. Obviously by calling a function in this way you're circumventing things like const-safety and so on, so these aspects are implemented as runtime checks.

I'm sure the syntax could be improved, and it only works on Win32 and Win64 so far. We've found it really useful for having automatic GUI interfaces to classes, creating properties in C++, streaming to and from XML and so on, and there's no need to derive from a specific base class. If there's enough demand maybe we could knock it into shape for release.

查看更多
公子世无双
5楼-- · 2018-12-31 03:00

RTTI doesn't exist for C++.

This is simply wrong. Actually, the very term “RTTI” was coined by the C++ standard. On the other hand, RTTI doesn't go very far in implementing reflection.

查看更多
柔情千种
6楼-- · 2018-12-31 03:00

Reflection in C++ is very useful, in cases there you need to run some method for each member(For example: serialization, hashing, compare). I came with generic solution, with very simple syntax:

struct S1
{
    ENUMERATE_MEMBERS(str,i);
    std::string str;
    int i;
};
struct S2
{
    ENUMERATE_MEMBERS(s1,i2);
    S1 s1;
    int i2;
};

Where ENUMERATE_MEMBERS is a macro, which is described later(UPDATE):

Assume we have defined serialization function for int and std::string like this:

void EnumerateWith(BinaryWriter & writer, int val)
{
    //store integer
    writer.WriteBuffer(&val, sizeof(int));
}
void EnumerateWith(BinaryWriter & writer, std::string val)
{
    //store string
    writer.WriteBuffer(val.c_str(), val.size());
}

And we have generic function near the "secret macro" ;)

template<typename TWriter, typename T>
auto EnumerateWith(TWriter && writer, T && val) -> is_enumerable_t<T>
{
    val.EnumerateWith(write); //method generated by ENUMERATE_MEMBERS macro
}

Now you can write

S1 s1;
S2 s2;
//....
BinaryWriter writer("serialized.bin");

EnumerateWith(writer, s1); //this will call EnumerateWith for all members of S1
EnumerateWith(writer, s2); //this will call EnumerateWith for all members of S2 and S2::s1 (recursively)

So having ENUMERATE_MEMBERS macro in struct definition, you can build serialization, compare, hashing, and other stuffs without touching original type, the only requirement is to implement "EnumerateWith" method for each type, which is not enumerable, per enumerator(like BinaryWriter). Usually you will have to implement 10-20 "simple" types to support any type in your project.

This macro should have zero-overhead to struct creation/destruction in run-time, and the code of T.EnumerateWith() should be generated on-demand, which can be achieved by making it template-inline function, so the only overhead in all the story is to add ENUMERATE_MEMBERS(m1,m2,m3...) to each struct, while implementing specific method per member type is a must in any solution, so I do not assume it as overhead.

UPDATE: There is very simple implementation of ENUMERATE_MEMBERS macro(however it could be a little be extended to support inheritance from enumerable struct)

#define ENUMERATE_MEMBERS(...) \
template<typename TEnumerator> inline void EnumerateWith(TEnumerator & enumerator) const { EnumerateWithHelper(enumerator, __VA_ARGS__ ); }\
template<typename TEnumerator> inline void EnumerateWith(TEnumerator & enumerator) { EnumerateWithHelper(enumerator, __VA_ARGS__); }

// EnumerateWithHelper
template<typename TEnumerator, typename ...T> inline void EnumerateWithHelper(TEnumerator & enumerator, T &...v) 
{ 
    int x[] = { (EnumerateWith(enumerator, v), 1)... }; 
}

// Generic EnumerateWith
template<typename TEnumerator, typename T>
auto EnumerateWith(TEnumerator & enumerator, T & val) -> std::void_t<decltype(val.EnumerateWith(enumerator))>
{
    val.EnumerateWith(enumerator);
}

And you do not need any 3rd party library for these 15 lines of code ;)

查看更多
君临天下
7楼-- · 2018-12-31 03:00

A simple way is to use the dynamic_cast<>() operator which, when assigned to an wrong type, returns NULL, so you can upcast to a base concrete class in an easy way, checking the value of the pointer, if it is not NULL, the cast was done, and you got the type of the object.

But this is just a simple solution, and it only provides the type of the objects, you cannot ask what methods it has, like in Java. If you need an advanced solution, there are some frameworks to choose from.

查看更多
登录 后发表回答