我有一个多个类,每个类都有不同的成员变量,这些变量在构造函数中被简单地初始化。这是一个例子:
struct Person
{
Person(const char *name, int age)
:
name(name),
age(age)
{
}
private:
const char *name;
int age;
};
每个都有一个关联的print<>()
函数。
template <>
void print<Person>(const Person &person)
{
std::cout << "name=" << name << "\n";
std::cout << "age=" << age << "\n";
}
由于参数列表在四个位置复制,因此该代码容易出错。如何重写代码以避免重复?我想使用预处理器和/或模板。
例如,我可以使用X-args预处理器技术 - 类似这样吗?
#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
或者更好,基于模板的方法?
请不要质疑我为什么要这样做,有合理的设计决策导致多个具有命名参数的类似对象。出于性能原因,参数需要被命名为成员变量。我只是在探索是否可以只列出一次参数及其类型。
答案 0 :(得分:194)
您需要做的是让预处理器生成有关字段的反射数据。此数据可以存储为嵌套类。
首先,为了使它在预处理器中更容易和更清晰,我们将使用类型化表达式。类型化表达式只是将类型放在括号中的表达式。因此,不是写int x
而是写(int) x
。这里有一些方便的宏来帮助输入类型:
#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
宏来生成有关每个字段的数据(加上字段本身)。这个宏将被调用如下:
REFLECTABLE
(
(const char *) name,
(int) age
)
因此,使用Boost.PP我们遍历每个参数并生成如下数据:
// 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
类的朋友,即使它们是私有的,它也可以访问这些字段:
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范围,并访问该索引处的字段数据。然后它将字段数据传递给用户提供的访问者:
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
类:
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
瞧,我们刚刚用100行代码在C ++中实现了反射。
答案 1 :(得分:6)
我已经解决了我的通用结构与JSON代码相同的问题。
定义宏:REFLECT(CLASS_NAME,MEMBER_SEQUENCE) 其中MEMBER_SEQUENCE是(姓名)(年龄)(其他)(...)
将REFLECT扩展为类似于:
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扩展为访问者。
然后定义您的打印访问者:
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_mace_reflect__typeinfo.html
https://github.com/bytemaster/mace/blob/master/libs/reflect/include/mace/reflect/reflect.hpp
答案 2 :(得分:4)
我担心您的解决方案对于这种减少的用例来说非常理想。我们可以提供帮助的地方是,除了print
之外还有其他功能可以从迭代字段中受益。
这是Boost.Fusion Fusion Sequences的完美示例;它们可用于引入编译时反射。最重要的是,您可以生成更多通用的运行时行为。
因此,您可以使用Fusion.Map(将您限制为每种类型的单个事件)或其他此类幻想来声明您的元素。
如果您的类型不符合Fusion序列(或者您不想干涉其内部),adapted部分中有适配器,例如BOOST_FUSION_ADAPT_STRUCT
。当然,既然不是所有东西都是struct
(或者有公共成员),那么对于类来说也有一个更通用的版本,它很快就会变得丑陋:BOOST_FUSION_ADAPT_ADT
。
从quick start偷窃
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());
}
适配器将让你“适应”一种类型,所以你会得到:
struct Foo { int bar; char const* buzz; };
BOOST_FUSION_ADAPT_STRUCT(
Foo,
(int, bar)
(char const*, buzz)
)
然后:
int main() {
Foo foo{1, "Hello");
for_each(foo, print_xml());
}
这是一个令人印象深刻的图书馆:)
答案 3 :(得分:2)
为什么需要使用预处理器? boost.fusion库简介的示例有点类似于您的用例。
答案 4 :(得分:1)
你需要一个元组,而不是一个类。这样可以轻松解决所有问题,而无需诉诸预处理器hackery。
答案 5 :(得分:1)
这是我2美分作为保罗伟大的REFLECTABLE()
宏的补充。我需要有一个空的字段列表,即// http://stackoverflow.com/a/2831966/2725810
#define REFLECTABLE_0(...) \
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 REFLECTABLE_1(...) \
static const int fields_n = 0;
#define REFLECTABLE_CONST2(b, ...) REFLECTABLE_##b(__VA_ARGS__)
#define REFLECTABLE_CONST(b, ...) REFLECTABLE_CONST2(b,__VA_ARGS__)
#define REFLECTABLE(...) \
REFLECTABLE_CONST(BOOST_PP_IS_EMPTY(__VA_ARGS__), __VA_ARGS__)
,以正确处理继承层次结构。以下修改处理了这种情况:
{{1}}