C++预处理器:避免成员变量列表的代码重复 [英] C++ preprocessor: avoid code repetition of member variable list

查看:24
本文介绍了C++预处理器:避免成员变量列表的代码重复的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有多个类,每个类都有不同的成员变量,这些成员变量在构造函数中被简单地初始化.下面是一个例子:

I have a multiple classes each with different member variables that are initialized trivially in a constructor. Here is an example:

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

每个都有一个关联的print<>()函数.

Each has an associated print<>() function.

template <>
void print<Person>(const Person &person)
{
    std::cout << "name=" << name << "\n";
    std::cout << "age=" << age << "\n";
}

此代码容易出错,因为参数列表在四个地方复制.如何重写代码以避免这种重复?我想使用预处理器和/或模板.

This code is error prone since the parameter list is replicated in four places. How can I rewrite the code to avoid this duplication? I'd like to use the preprocessor and/or templates.

例如,我可以使用 X-args 预处理器技术——像这样吗?

For example, could I use the X-args preprocessor technique -- something like this?

#define ARGUMENTS \
    ARG(const char *, name) \
    ARG(int, age)

struct Person
{
    Person(LIST_TYPE_NAME_COMMA(ARGUMENTS))
       :
       LIST_NAME_INIT(ARGUMENTS)
    {
    }
private:
    LIST_TYPE_NAME_SEMICOLON(ARGUMENTS)
};

template <>
void print<Person>(const Person &person)
{
   LIST_COUT_LINE(ARGUMENTS)
}

#undef ARGUMENTS

或者更好的是,基于模板的方法?

Or better, a template-based approach?

请不要质疑我为什么要这样做,有一些合理的设计决策导致了多个具有命名参数的相似对象.出于性能原因,参数需要命名为成员变量.我只是在探索是否可以只列出一次参数及其类型.

Please don't question why I want to do this, there are reasoned design decisions that have resulted in multiple similar objects with named parameters. The parameters need to be named member variables for performance reasons. I'm just exploring whether it's possible to list the parameters and their types only once.

推荐答案

您需要做的是让预处理器生成有关字段的反射数据.此数据可以存储为嵌套类.

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

首先,为了在预处理器中更容易、更清晰地编写它,我们将使用类型化表达式.类型表达式只是将类型放在括号中的表达式.所以不是写int x,你会写(int) x.这里有一些方便的宏来帮助处理类型化表达式:

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

接下来,我们定义一个 REFLECTABLE 宏来生成每个字段的数据(加上字段本身).这个宏会被这样调用:

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
)

所以使用 Boost.PP 我们迭代每个参数并生成如下数据:

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

这样做是生成一个常量fields_n,它是类中可反射字段的数量.然后它为每个字段专门化 field_data.它也是 reflector 类的朋友,这样它就可以访问字段,即使它们是私有的:

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

现在迭代我们使用访问者模式的字段.我们创建一个从 0 到字段数的 MPL 范围,并访问该索引处的字段数据.然后它将字段数据传递给用户提供的访问者:

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 T>
    void operator()(C& c, Visitor v, T)
    {
        v(reflector::get_field_data<T::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));
}

现在到了关键时刻,我们将所有内容放在一起.下面是我们如何定义 Person 类:

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

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

这是通用的print_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());
}

示例:

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

输出:

name=Tom
age=82

瞧,我们刚刚在 C++ 中实现了反射,不到 100 行代码.

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

这篇关于C++预处理器:避免成员变量列表的代码重复的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆