2012-06-14 20 views
54

私は、コンストラクタ内でほとんど初期化された異なるメンバ変数を持つ複数のクラスを持っています。次に例を示します。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"; 
} 

このコードは、パラメータリストが4か所で複製されるため、エラーが発生しやすくなります。この重複を避けるために、コードをどのように書き換えることができますか?プリプロセッサやテンプレートを使用したいと思います。

例えば、私は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 

また、テンプレートベースのアプローチですか?

なぜ私はこれをやりたいのでしょうかと疑問に思ってはいけません。パフォーマンス上の理由から、パラメータにはメンバ変数を指定する必要があります。パラメータとその型を1回だけリストすることが可能かどうかを調べています。

+2

C++ 11の機能を使用できますか?あなたはイニシャライザリストや一様な初期化を調べたいかもしれません。この問題に対処するのに役立つかもしれません(https://en.wikipedia.org/wiki/C%2B%2B11#Core_language_usability_enhancementsを参照してください) – piwi

+0

文字列の代わりに文字列へのポインタを格納するエラーが発生しやすくなります。常にポインタが悪くならないようにします。つまり、依存関係を導入します。 std :: stringを使用して 'name'のコピーを格納する方が良い –

+2

@AndersKの 'const char *'は例のためだけです – paperjam

答えて

187

は何をする必要がプリプロセッサはフィールドの反射データを生成しています。このデータはネストされたクラスとして格納できます。

まず、プリプロセッサに書き込むのを簡単にし、よりクリーンにするために、型付き式を使用します。型付き式は、その型を括弧でくくる式です。したがって、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)); 
} 

真実の瞬間、すべてをまとめています。ここで

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

一般print_fields機能は次のとおりです:ここでは、Personクラスを定義することができる方法である

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++でリフレクションを実装しました。

+18

これは、stackoverflow上の最高の答えのための何らかの賞を獲得する必要があります!非常に印象的なマクロブードゥーここです。 –

+40

+1:これは恐ろしくて恐ろしいことです。私はそれが好きです! –

+0

@Paul:印象深い!コピーを避けるためには1つのnit => 'operator()(T const&t)'が必要です。そうでなければ、高価になるでしょう。 –

6

ジェネリック構造体と同じ問題がJSONコードに解決されました。

マクロを定義します(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

+0

私は、私のソリューションがより堅牢で、「ベスト」ソリューションよりも速くコンパイルできることを指摘したいと思います。ユーザはより高度なリフレクション技術に使用できるメンバポインタを使用できます。明らかに、Paulのコードは変更することができますが、それはもっと冗長です。私は彼に、より多くのプリプロセッサの詳細を提供する小道具を与えます。 – bytemaster

1

クラスではなくタプルが必要です。これにより、プリプロセッサのハッカーに頼ることなく、すべての問題を簡単に解決できます。

+2

タプルは名前が付けられていないので、タプルにはアクセスできません。名前がないため、フィールド名をシリアル化することはできません。 –

+0

@Paulフュージョン::マップはどうですか? – gnzlbg

1

なぜプリプロセッサを使用する必要がありますか? boost.fusionライブラリの紹介は、あなたのユースケースにやや似た例です。

4

あなたのソリューションは、このような使用時間の削減には非常に最適です。我々が助けることができるのは、フィールドを反復することによって利益を得るprint以外の追加機能がある場合です。

これは、Boost.FusionFusion Sequencesの完全な例です。コンパイル時のリフレクションを導入するために使用できます。さらに、より一般的なランタイム動作を生成することができます。

たとえば、Fusion.Map(各タイプの単一のオカレンスに制限されます)などの要素を宣言することができます。

タイプがフュージョンシーケンスに適合していない(または内部に干渉したくない)場合はBOOST_FUSION_ADAPT_STRUCTなどのadaptedセクションにアダプタがあります。そしてもちろん、すべてがstruct(またはパブリックメンバーを持っている)ではないので、クラスのためのより一般的なバージョンもあります。すぐに醜いです:BOOST_FUSION_ADAPT_ADT

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

にですquick startから盗む

かなり印象的な図書館です。

0

ポールの偉大なREFLECTABLEマクロに加えて、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__) 
関連する問題