首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >C++预处理器:避免成员变量列表的代码重复

C++预处理器:避免成员变量列表的代码重复
EN

Stack Overflow用户
提问于 2012-06-14 18:19:23
回答 6查看 33.2K关注 0票数 64

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

代码语言:javascript
运行
复制
struct Person
{
    Person(const char *name, int age)
        :
        name(name),
        age(age)
    {
    }
private:
    const char *name;
    int age;
};

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

代码语言:javascript
运行
复制
template <>
void print<Person>(const Person &person)
{
    std::cout << "name=" << name << "\n";
    std::cout << "age=" << age << "\n";
}

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

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

代码语言:javascript
运行
复制
#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

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

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

EN

回答 6

Stack Overflow用户

回答已采纳

发布于 2012-08-01 00:29:42

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

首先,为了使在预处理器中编写它变得更容易、更简洁,我们将使用类型化表达式。类型化表达式只是将类型放在括号中的表达式。因此,您将编写(int) x,而不是编写int x。下面是一些方便的宏,可以帮助您处理类型化表达式:

代码语言:javascript
运行
复制
#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宏来生成有关每个字段(以及字段本身)的数据。这个宏将被这样调用:

代码语言:javascript
运行
复制
REFLECTABLE
(
    (const char *) name,
    (int) age
)

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

代码语言:javascript
运行
复制
// 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类的好友,这样即使字段是私有的,它也可以访问它们:

代码语言:javascript
运行
复制
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范围,并访问该索引处的字段数据。然后,它将字段数据传递给用户提供的访问者:

代码语言:javascript
运行
复制
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类:

代码语言:javascript
运行
复制
struct Person
{
    Person(const char *name, int age)
        :
        name(name),
        age(age)
    {
    }
private:
    REFLECTABLE
    (
        (const char *) name,
        (int) age
    )
};

下面是广义的print_fields函数:

代码语言:javascript
运行
复制
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());
}

举个例子:

代码语言:javascript
运行
复制
int main()
{
    Person p("Tom", 82);
    print_fields(p);
    return 0;
}

以下哪项输出:

代码语言:javascript
运行
复制
name=Tom
age=82

瞧,我们刚刚用C++实现了反射,用了不到100行代码。

票数 206
EN

Stack Overflow用户

发布于 2012-07-30 01:51:38

我已经用JSON代码的泛型结构解决了同样的问题。

定义宏: REFLECT( CLASS_NAME,MEMBER_SEQUENCE ) where MEMBER_SEQUENCE is (name)(age)(other)(...)

将反射扩展为类似以下内容:

代码语言:javascript
运行
复制
template<>
struct reflector<CLASS_NAME> {
  template<typename Visitor>
  void visit( Visitor&& v ) {
     v( "name" , &CLASS_NAME::name );
     v( "age",   &CLASS_NAME::age  );
     ... 
  }
}

您可以使用BOOST_PP_SEQ_FOREACH将SEQ扩展到访问者。

然后定义你的打印访问者:

代码语言:javascript
运行
复制
template<typename T>
struct print_visitor {
  print_visitor( T& s ):self(s){}

  template<typename R>
  void operator( const char* name, R (T::*member) )const {
     std::cout<<name<<"= "<<self.*member<<std::endl;
  } 
  T& self;
}

template<typename T>
void print( const T& val ) {
   reflector<T>::visit( print_visitor<T>(val) );
}

http://bytemaster.github.com/mace/group__reflect__typeinfo.html

https://github.com/bytemaster/mace/blob/master/libs/reflect/include/mace/reflect/reflect.hpp

票数 6
EN

Stack Overflow用户

发布于 2012-08-02 02:22:35

我担心你的解决方案对于这个减少的用例来说是非常理想的。我们可以提供帮助的是,如果您除了print之外还有额外的函数,可以从字段迭代中受益。

这是Boost.Fusion Fusion Sequences的一个完美示例;它们可以用来引入编译时反射。最重要的是,您可以生成更通用的运行时行为。

因此,例如,您可以使用Fusion.Map (它限制您只能在每种类型中出现一次)或其他此类幻想来声明您的元素。

如果您的类型不符合Fusion序列(或者您不想干预它的内部结构),那么在adapted部分中有一些适配器,比如BOOST_FUSION_ADAPT_STRUCT。当然,由于并不是所有的类都是struct (或者有公共成员),所以还有一个更通用的类版本,它很快就会变得丑陋起来:BOOST_FUSION_ADAPT_ADT

quick start窃取数据

代码语言:javascript
运行
复制
struct print_xml {
    template <typename T>
    void operator()(T const& x) const {
        std::cout
            << '<' << typeid(x).name() << '>'
            << x
            << "</" << typeid(x).name() << '>'
            ;
    }
};

int main() {
    vector<int, char, std::string> stuff(1, 'x', "howdy");
    int i = at_c<0>(stuff);
    char ch = at_c<1>(stuff);
    std::string s = at_c<2>(stuff);

    for_each(stuff, print_xml());
}

适配器将允许您“适配”一个类型,因此您将获得:

代码语言:javascript
运行
复制
struct Foo { int bar; char const* buzz; };

BOOST_FUSION_ADAPT_STRUCT(
    Foo,
    (int, bar)
    (char const*, buzz)
)

然后:

代码语言:javascript
运行
复制
int main() {
    Foo foo{1, "Hello");
    for_each(foo, print_xml());
}

这是一个令人印象深刻的库:)

票数 4
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/11031062

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档