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

前端 未结 28 1666
感动是毒
感动是毒 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: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
    struct make_const
    {
        typedef T type;
    };
    
    template
    struct make_const
    {
        typedef typename boost::add_const::type type;
    };
    
    
    #define REFLECTABLE(...) \
    static const int fields_n = BOOST_PP_VARIADIC_SIZE(__VA_ARGS__); \
    friend struct reflector; \
    template \
    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 \
    struct field_data \
    { \
        Self & self; \
        field_data(Self & self) : self(self) {} \
        \
        typename make_const::type & get() \
        { \
            return self.STRIP(x); \
        }\
        typename boost::add_const::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
        static typename T::template field_data get_field_data(T& x)
        {
            return typename T::template field_data(x);
        }
    
        // Get the number of fields
        template
        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
        void operator()(C& c, Visitor v, I)
        {
            v(reflector::get_field_data(c));
        }
    };
    
    
    template
    void visit_each(C & c, Visitor v)
    {
        typedef boost::mpl::range_c::n> range;
        boost::mpl::for_each(boost::bind(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
        void operator()(FieldData f)
        {
            std::cout << f.name() << "=" << f.get() << std::endl;
        }
    };
    
    template
    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.

提交回复
热议问题