IT박스

C ++ 전 처리기 : 멤버 변수 목록의 코드 반복 방지

itboxs 2020. 12. 6. 21:21
반응형

C ++ 전 처리기 : 멤버 변수 목록의 코드 반복 방지


생성자에서 사소하게 초기화되는 서로 다른 멤버 변수를 가진 여러 클래스가 있습니다. 다음은 그 예입니다.

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

아니면 템플릿 기반 접근법이 더 좋을까요?

왜 내가 이것을하고 싶은지 의문을 가지지 마십시오. 명명 된 매개 변수를 가진 여러 개의 유사한 객체를 초래 한 합리적인 디자인 결정이 있습니다. 성능상의 이유로 매개 변수는 이름이 멤버 변수로 지정되어야합니다. 매개 변수와 그 유형을 한 번만 나열 할 수 있는지 여부를 살펴보고 있습니다.


해야 할 일은 전처리 기가 필드에 대한 반사 데이터를 생성하도록하는 것입니다. 이 데이터는 중첩 된 클래스로 저장할 수 있습니다.

첫째, 전처리기에 더 쉽고 깔끔하게 작성하기 위해 형식화 된 표현식을 사용합니다. 형식화 된 식은 형식을 괄호 안에 넣는 식입니다. 따라서 글 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 ++로 리플렉션을 구현했습니다.


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


I am afraid that your solution is pretty optimal for this reduced usecase. Where we can help is if you have additional functions besides print that would benefit from iterating over the fields.

This is a perfect example for Boost.Fusion Fusion Sequences; they can be used to introduce compile-time reflection. On top of it you can then generate more generic runtime behavior.

So, you can for example declare your elements using a Fusion.Map (which restricts you to a single occurrence of each type) or other such fantasies.

If your type does not conform to a Fusion Sequence (or you don't want to meddle with its internals), there are adapters in the adapted section such as BOOST_FUSION_ADAPT_STRUCT. And of course, since not everything is a struct (or has public members), there is also a more generic version for classes, it just gets ugly soon: BOOST_FUSION_ADAPT_ADT.

Stealing from the 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());
}

The adapters will let you "adapt" a type, so you would get:

struct Foo { int bar; char const* buzz; };

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

And then:

int main() {
    Foo foo{1, "Hello");
    for_each(foo, print_xml());
}

It's a pretty impressive library :)


Why do you need to use preprocessor? Introduction to boost.fusion library has an example somewhat similar to your use-case.


You need a tuple, not a class. This would easily solve all of your problems without having to resort to preprocessor hackery.


Here are my 2 cents as an addition to the great REFLECTABLE macro of Paul. I had a need to have an empty list of fields, i.e. REFLECTABLE(), to handle an inheritance hierarchy properly. The following modification handles this case:

// 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__) 

참고URL : https://stackoverflow.com/questions/11031062/c-preprocessor-avoid-code-repetition-of-member-variable-list

반응형