3

この質問は非常に漠然としている可能性があることを知っています。これは、私がここで提供した回答を少し拡張したものです。基本的に、私は個人的なプロジェクトとして、データベース作業を行う C# プログラマーに人気のある「コード ファースト」イディオムを C++ で再現しようと試みてきました。すべてが素晴らしく、うまく機能します。

C# の優れた点の 1 つは、属性などです。C++ にはそのような目新しさはないので、データベースで構造化される特定のクラスのコードは次のようになります。

class User : public Record {
public:
    User(Model& m) : Record(L"_user", m)
                   , Username(L"_userName", 32, false)
                   , Nickname(L"_nickName", 32, false) {
        field(Username);
        field(Nickname);
    };  // eo ctor

    Field<String> Username;
    Field<String> Nickname;
};  // eo class User

field理想的には、コンストラクター内の呼び出しを取り除きたいです。基本的に、彼らが行うことは、実際の作業を行う基本クラスにフィールドを登録することだけです。この種のことを達成し、コンストラクターで指定したのと同じくらい簡単に基本クラスに詳細を渡すにはどうすればよいでしょうか?

私が答えた前の質問では、C++ が物事を構築する方法のおかげで、基底クラスの情報を提供することができました。fieldこれをコンストラクターで活用して、プログラマーのエラーを引き起こす可能性のある厄介な呼び出しを取り除く方法はありますか?

この時点で、すべてがうまく機能していることに満足していますが、混乱を減らし、コンストラクターでのこの「登録」プロセスの必要性を取り除き、何らかの方法でそれらを基本クラスに自動的に登録したいと考えています。

すべての意見を歓迎します:)

編集

したがって、の実装fieldは次のとおりです。

        void Record::field(detail::field_base& field) {
            field.owner_ = this;
            fields_.push_back(&field);
        };  // eo field**
4

3 に答える 3

4

Field<>コンストラクターに、への追加のポインターを受け入れ、コンストラクターの本体でそのポインターをRecord呼び出すようにすることができます。field(*this)

template<typename T>
struct Field // Just guessing...
{
    Field(std::string s, int i, bool b, Record* pRecord) 
    // ...
    { 
        pRecord->field(*this); 
    }
    // ...
};

次に、初期化リストで、次のように追加のポインタとしてUser渡します。thisRecord

User(Model& m) : Record(L"_user", m)
               , Username(L"_userName", 32, false, this)
               , Nickname(L"_nickName", 32, false, this)
{
};  // eo ctor
于 2013-03-14T00:12:15.730 に答える
2

必要な情報、つまりthisポインターをFieldコンストラクターに渡して、コンストラクターに作業を任せるだけです。

このための特別なラッパークラスを導入することもできます

于 2013-03-14T00:11:21.450 に答える
2

属性構文にとらわれないでください。

あなたが望むのは、structs を実行時のリフレクションで意味のある名前のデータの集合体として扱う機能ですよね?

新しい名前を導入するには、型を使用します。

template<typename T> struct FieldTag {};
struct Username:FieldTag<Username> {} username;
struct Nickname:FieldTag<Nickname> {} nickname;

型の集約を生成するには、テンプレート引数を使用します。

template<typename Child, typename Field>
struct FieldHandler {
  typedef typename Field::tag_type tag_type;
  typedef typename Field::value_type value_type;
  PsuedoRef<value_type> operator[]( tag_type ) { /* details */ }
  /* details */
};
template<typename... Fields>
struct Aggregate:
  Record,
  FieldHandler<Aggregate<Fields...>, Fields>...
{
  /* details */
};
template<typename Value, typename Tag>
struct Field {
  typedef Tag tag_type;
  typedef Value value_type;
};

上記のすべてのボイラープレートといくつかの演算子オーバーライドの乱用の目的は、これから美しい構文を取得できるようにすることです。最終的なプログラマーは、ボイラープレートなしで C++ によく似た構文を取得できます。

Data2 つのフィールドを持つ型を作成する方法の最終目標は次のとおりです。

struct Data:
  Aggregate<
    Field<std::string, Username>,
    Field<std::string, Nickname>
  >
{
  Data(): Aggregate(
    username|L"username" = L"Unknown Name",
    nickname|L"nickname" = L"Unknown NickName"
  {}
};

フィールドにアクセスするには、適切にオーバーライドされた演算子を使用し^ます->:

Data d;
d^username= L"Bob";
d^nickname= L"Apples";

これはすべて C++11 で可能ですが、それほど簡単ではありません。

現在、関数へのポインターをテンプレート引数として受け取る機能と、関数に変換可能なステートレス ラムダを組み合わせることで、文字列をフィールドの型に格納できるようになり、構築時に文字列を渡す必要がなくなります。一方、評価されていないコンテキストに対する制限は、それを疑わしいものにします。

それで、それは残りの1つの醜いビットです。さらに、上記の作業を行うために必要な労力を考えると、各フィールドの煩わしいボイラープレートを飲み込む必要があるのではないかと思います。

上記のコードは単なる疑似コードであり、実行できるルートのスケッチであり、完全に近いわけではないことに注意してください。C++ のテンプレート メタプログラミング サブ言語を学習する森の中で迷子になり、問題を解決することを決して回避したくない場合を除き、このルートをたどろうとしないことをお勧めします。

もう一度言いますが、テンプレート メタプログラミングを学ぶことは、現在のプロジェクトよりも楽しくありませんか? ;)

于 2013-03-14T00:27:25.843 に答える