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

前端 未结 28 1654
感动是毒
感动是毒 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:47

    If you declare a pointer to a function like this:

    int (*func)(int a, int b);
    

    You can assign a place in memory to that function like this (requires libdl and dlopen)

    #include <dlfcn.h>
    
    int main(void)
    {
        void *handle;
        char *func_name = "bla_bla_bla";
        handle = dlopen("foo.so", RTLD_LAZY);
        *(void **)(&func) = dlsym(handle, func_name);
        return func(1,2);
    }
    

    To load a local symbol using indirection, you can use dlopen on the calling binary (argv[0]).

    The only requirement for this (other than dlopen(), libdl, and dlfcn.h) is knowing the arguments and type of the function.

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

    even though reflection is not supported out-of-the-box in c++, it is not too hard to implement. I've encountered this great article: http://replicaisland.blogspot.co.il/2010/11/building-reflective-object-system-in-c.html

    the article explains in great detail how you can implement a pretty simple and rudimentary reflection system. granted its not the most wholesome solution, and there are rough edges left to be sorted out but for my needs it was sufficient.

    the bottom line - reflection can pay off if done correctly, and it is completely feasible in c++.

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

    You need to look at what you are trying to do, and if RTTI will satisfy your requirements. I've implemented my own pseudo-reflection for some very specific purposes. For example, I once wanted to be able to flexibly configure what a simulation would output. It required adding some boilerplate code to the classes that would be output:

    namespace {
      static bool b2 = Filter::Filterable<const MyObj>::Register("MyObject");
    } 
    
    bool MyObj::BuildMap()
    {
      Filterable<const OutputDisease>::AddAccess("time", &MyObj::time);
      Filterable<const OutputDisease>::AddAccess("person", &MyObj::id);
      return true;
    }
    

    The first call adds this object to the filtering system, which calls the BuildMap() method to figure out what methods are available.

    Then, in the config file, you can do something like this:

    FILTER-OUTPUT-OBJECT   MyObject
    FILTER-OUTPUT-FILENAME file.txt
    FILTER-CLAUSE-1        person == 1773
    FILTER-CLAUSE-2        time > 2000
    

    Through some template magic involving boost, this gets translated into a series of method calls at run-time (when the config file is read), so it's fairly efficient. I wouldn't recommend doing this unless you really need to, but, when you do, you can do some really cool stuff.

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

    There is another new library for reflection in C++, called RTTR (Run Time Type Reflection, see also github).

    The interface is similar to reflection in C# and it works without any RTTI.

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

    What you need to do is have the preprocessor generate reflection data about the fields. This data can be stored as nested classes.

    First, to make it easier and cleaner to write it in the preprocessor we will use typed expression. A typed expression is just an expression that puts the type in parenthesis. So instead of writing int x you will write (int) x. Here are some handy macros to help with typed expressions:

    #define REM(...) __VA_ARGS__
    #define EAT(...)
    
    // Retrieve the type
    #define TYPEOF(x) DETAIL_TYPEOF(DETAIL_TYPEOF_PROBE x,)
    #define DETAIL_TYPEOF(...) DETAIL_TYPEOF_HEAD(__VA_ARGS__)
    #define DETAIL_TYPEOF_HEAD(x, ...) REM x
    #define DETAIL_TYPEOF_PROBE(...) (__VA_ARGS__),
    // Strip off the type
    #define STRIP(x) EAT x
    // Show the type without parenthesis
    #define PAIR(x) REM x
    

    Next, we define a REFLECTABLE macro to generate the data about each field(plus the field itself). This macro will be called like this:

    REFLECTABLE
    (
        (const char *) name,
        (int) age
    )
    

    So using Boost.PP we iterate over each argument and generate the data like this:

    // A helper metafunction for adding const to a type
    template<class M, class T>
    struct make_const
    {
        typedef T type;
    };
    
    template<class M, class T>
    struct make_const<const M, T>
    {
        typedef typename boost::add_const<T>::type type;
    };
    
    
    #define REFLECTABLE(...) \
    static const int fields_n = BOOST_PP_VARIADIC_SIZE(__VA_ARGS__); \
    friend struct reflector; \
    template<int N, class Self> \
    struct field_data {}; \
    BOOST_PP_SEQ_FOR_EACH_I(REFLECT_EACH, data, BOOST_PP_VARIADIC_TO_SEQ(__VA_ARGS__))
    
    #define REFLECT_EACH(r, data, i, x) \
    PAIR(x); \
    template<class Self> \
    struct field_data<i, Self> \
    { \
        Self & self; \
        field_data(Self & self) : self(self) {} \
        \
        typename make_const<Self, TYPEOF(x)>::type & get() \
        { \
            return self.STRIP(x); \
        }\
        typename boost::add_const<TYPEOF(x)>::type & get() const \
        { \
            return self.STRIP(x); \
        }\
        const char * name() const \
        {\
            return BOOST_PP_STRINGIZE(STRIP(x)); \
        } \
    }; \
    

    What this does is generate a constant fields_n that is number of reflectable fields in the class. Then it specializes the field_data for each field. It also friends the reflector class, this is so it can access the fields even when they are private:

    struct reflector
    {
        //Get field_data at index N
        template<int N, class T>
        static typename T::template field_data<N, T> get_field_data(T& x)
        {
            return typename T::template field_data<N, T>(x);
        }
    
        // Get the number of fields
        template<class T>
        struct fields
        {
            static const int n = T::fields_n;
        };
    };
    

    Now to iterate over the fields we use the visitor pattern. We create an MPL range from 0 to the number of fields, and access the field data at that index. Then it passes the field data on to the user-provided visitor:

    struct field_visitor
    {
        template<class C, class Visitor, class I>
        void operator()(C& c, Visitor v, I)
        {
            v(reflector::get_field_data<I::value>(c));
        }
    };
    
    
    template<class C, class Visitor>
    void visit_each(C & c, Visitor v)
    {
        typedef boost::mpl::range_c<int,0,reflector::fields<C>::n> range;
        boost::mpl::for_each<range>(boost::bind<void>(field_visitor(), boost::ref(c), v, _1));
    }
    

    Now for the moment of truth we put it all together. Here is how we can define a Person class that is reflectable:

    struct Person
    {
        Person(const char *name, int age)
            :
            name(name),
            age(age)
        {
        }
    private:
        REFLECTABLE
        (
            (const char *) name,
            (int) age
        )
    };
    

    Here is a generalized print_fields function using the reflection data to iterate over the fields:

    struct print_visitor
    {
        template<class FieldData>
        void operator()(FieldData f)
        {
            std::cout << f.name() << "=" << f.get() << std::endl;
        }
    };
    
    template<class T>
    void print_fields(T & x)
    {
        visit_each(x, print_visitor());
    }
    

    An example of using the print_fields with the reflectable Person class:

    int main()
    {
        Person p("Tom", 82);
        print_fields(p);
        return 0;
    }
    

    Which outputs:

    name=Tom
    age=82
    

    And voila, we have just implemented reflection in C++, in under 100 lines of code.

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

    The RareCpp library makes for fairly easy and intuitive reflection - all field/type information is designed to either be available in arrays or to feel like array access. It's written for C++17 and works with Visual Studios, g++, and Clang. The library is header only, meaning you need only copy "Reflect.h" into your project to use it.

    Reflected structs or classes need the REFLECT macro, where you supply the name of the class you're reflecting and the names of the fields.

    class FuelTank {
        public:
            float capacity;
            float currentLevel;
            float tickMarks[2];
    
        REFLECT(FuelTank, capacity, currentLevel, tickMarks)
    };
    

    That's all there is, no additional code is needed to setup reflection. Optionally you can supply superclasses (in the parenthesis of the first argument) and field annotations (in the parenthesis preceeding the field you want to annotate) to be able to traverse superclasses or add additional compile-time information to a field (such as Json::Ignore).

    Looping through fields can be as simple as...

    for ( size_t i=0; i<FuelTank::Class::TotalFields; i++ )
        std::cout << FuelTank::Class::Fields[i].name << std::endl;
    

    You can loop through an object instance to access field values (which you can read or modify) and field type information...

    FuelTank::Class::ForEachField(fuelTank, [&](auto & field, auto & value) {
        using Type = typename std::remove_reference<decltype(value)>::type;
        std::cout << TypeToStr<Type>() << " " << field.name << ": " << value << std::endl;
    });
    

    A JSON Library is built on top of RandomAccessReflection which auto identifies appropriate JSON output representations for reading or writing, and can recursively traverse any reflected fields, as well as arrays and STL containers.

    struct MyOtherObject { int myOtherInt; REFLECT(MyOtherObject, myOtherInt) };
    struct MyObject
    {
        int myInt;
        std::string myString;
        MyOtherObject myOtherObject;
        std::vector<int> myIntCollection;
    
        REFLECT(MyObject, myInt, myString, myOtherObject, myIntCollection)
    };
    
    int main()
    {
        MyObject myObject = {};
        std::cout << "Enter MyObject:" << std::endl;
        std::cin >> Json::in(myObject);
        std::cout << std::endl << std::endl << "You entered:" << std::endl;
        std::cout << Json::pretty(myObject);
    }
    

    The above could be ran like so...

    Enter MyObject:
    {
      "myInt": 1337, "myString": "stringy", "myIntCollection": [2,4,6],
      "myOtherObject": {
        "myOtherInt": 9001
      }
    }
    
    
    You entered:
    {
      "myInt": 1337,
      "myString": "stringy",
      "myOtherObject": {
        "myOtherInt": 9001
      },
      "myIntCollection": [ 2, 4, 6 ]
    }
    

    See also...

    • Reflect Documentation
    • Reflect Implementation
    • More Usage Examples
    0 讨论(0)
提交回复
热议问题