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

前端 未结 28 1603
感动是毒
感动是毒 2020-11-21 11:25

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

相关标签:
28条回答
  • 2020-11-21 11:36

    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 ;)

    0 讨论(0)
  • 2020-11-21 11:38

    I would like to advertise the existence of the automatic introspection/reflection toolkit "IDK". It uses a meta-compiler like Qt's and adds meta information directly into object files. It is claimed to be easy to use. No external dependencies. It even allows you to automatically reflect std::string and then use it in scripts. Please look at IDK

    0 讨论(0)
  • 2020-11-21 11:40

    The two reflection-like solutions I know of from my C++ days are:

    1) Use RTTI, which will provide a bootstrap for you to build your reflection-like behaviour, if you are able to get all your classes to derive from an 'object' base class. That class could provide some methods like GetMethod, GetBaseClass etc. As for how those methods work you will need to manually add some macros to decorate your types, which behind the scenes create metadata in the type to provide answers to GetMethods etc.

    2) Another option, if you have access to the compiler objects is to use the DIA SDK. If I remember correctly this lets you open pdbs, which should contain metadata for your C++ types. It might be enough to do what you need. This page shows how you can get all base types of a class for example.

    Both these solution are a bit ugly though! There is nothing like a bit of C++ to make you appreciate the luxuries of C#.

    Good Luck.

    0 讨论(0)
  • 2020-11-21 11:41

    I would recommend using Qt.

    There is an open-source licence as well as a commercial licence.

    0 讨论(0)
  • 2020-11-21 11:41

    EDIT: CAMP is no more maintained ; two forks are available:

    • One is also called CAMP too, and is based on the same API.
    • Ponder is a partial rewrite, and shall be preferred as it does not requires Boost ; it's using C++11.

    CAMP is an MIT licensed library (formerly LGPL) that adds reflection to the C++ language. It doesn't require a specific preprocessing step in the compilation, but the binding has to be made manually.

    The current Tegesoft library uses Boost, but there is also a fork using C++11 that no longer requires Boost.

    0 讨论(0)
  • 2020-11-21 11:42

    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.

    0 讨论(0)
提交回复
热议问题