2

これが私を悩ませていることです: 必須かそうでないかの属性のセットを持つクラスの階層があります。これらのクラスは、XML (またはその他のもの) にシリアライズされます。必須の属性は常にシリアル化する必要があり、そうでない属性は指定した場合にのみシリアル化する必要があります。

各属性に値が設定されているかどうかを具体的にチェックすることなく、このプロセスを可能な限り自動化する方法を探しています。

以下に例を示します。

 class Book
 {

     QString m_author;           // this is optional
     QString m_secondaryAuthor;  // this is optional
     QString m_title;            // this is mandatory

 public: 
     QString serializePlainText()
     {
         QString result = "Book:";
         result += m_title;
         if(m_author.length()) result+= " by "+m_author;
         if(m_SecondaryAuthor.length()) result+= " and "+m_SecondaryAuthor;
         return result;
     }

     void setTitle(const QString& title)
     {
         m_title = title;
     }

     void setAuthor(const QString& author)
     {
         m_author = author;
     }
 };

もちろん、これは単純な例にすぎません。実際の生活はもっと複雑で、クラスは何百もの属性を持つことができます。そして、ここで質問の 2 番目の部分に入ります。より厳密な方法で属性を設定できるようにする必要があります。文字列属性に値をマップするマップを使用せずに、多かれ少なかれ上記の例のように(セッター関数を使用してsetTitle(const QString&)...など....

これを行うための気の利いた方法は次のとおりです。

  1. すべての属性を手動で宣言して確認する必要はありません
  2. 見栄えが良く、プログラマーフレンドリーな API をお持ちですか?

私は多かれ少なかれQtにこだわっていますが、これは必須ではありません。

ありがとう。

4

2 に答える 2

1

プリプロセッサを使用してコードを生成したい場合は、Boost.Preprocessorに頼ることができます。たとえば、次の方法で何かを行うことができます。

#define GET_TYPE( pair ) BOOST_PP_TUPLE_ELEM( 2, 0, pair )
#define GET_NAME( pair ) BOOST_PP_TUPLE_ELEM( 2, 1, pair )

#define DECLARE_MANDATORY( r, data, elem ) GET_TYPE( elem ) GET_NAME( elem );

#define DECLARE_MANDATORY_ATTRIBUTES( attributes ) \
    BOOST_PP_SEQ_FOR_EACH( DECLARE_MANDATORY, ~, attributes )

#define DECLARE_OPTIONAL( r, data, elem ) boost::optional< GET_TYPE( elem ) > GET_NAME( elem );

#define DECLARE_OPTIONAL_ATTRIBUTES( attributes ) \
    BOOST_PP_SEQ_FOR_EACH( DECLARE_OPTIONAL, ~, attributes )

#define MANDATORY_ACCESSORS( r, data, elem ) \
    BOOST_PP_CAT( void set_, GET_NAME( elem ) ) (GET_TYPE( elem ) const & value) { GET_NAME( elem ) = value; } \
    GET_TYPE( elem ) BOOST_PP_CAT( get_, GET_NAME( elem ) ) () const { return GET_NAME( elem ); }

#define DEFINE_MANDATORY_ACCESSORS( attributes ) \
    BOOST_PP_SEQ_FOR_EACH( MANDATORY_ACCESSORS, ~, attributes )

#define OPTIONAL_ACCESSORS( r, data, elem ) \
    BOOST_PP_CAT( void set_, GET_NAME( elem ) ) (GET_TYPE( elem ) const & value) { GET_NAME( elem ).reset(value); } \
    GET_TYPE( elem ) BOOST_PP_CAT( get_, GET_NAME( elem ) ) () const { return *GET_NAME( elem ); }

#define DEFINE_OPTIONAL_ACCESSORS( attributes ) \
    BOOST_PP_SEQ_FOR_EACH( OPTIONAL_ACCESSORS, ~, attributes )

class Book
{
    #define BOOK_MANDATORY_ATTRIBUTES ((std::string, title))
    #define BOOK_OPTIONAL_ATTRIBUTES ((std::string, author)) ((std::string, secondaryAuthor))

  public:

    DECLARE_MANDATORY_ATTRIBUTES( BOOK_MANDATORY_ATTRIBUTES )
    DECLARE_OPTIONAL_ATTRIBUTES( BOOK_OPTIONAL_ATTRIBUTES )

    DEFINE_MANDATORY_ACCESSORS( BOOK_MANDATORY_ATTRIBUTES )
    DEFINE_OPTIONAL_ACCESSORS( BOOK_OPTIONAL_ATTRIBUTES )
};

int main()
{
    Book b;
    b.set_title("H2G2");

    std::cout << b.get_title();

    b.set_author("Douglas Adams");
    std::cout << b.get_author();
}

別のマクロを作成して、[ boost::optional](http://www.boost.org/libs/optional] の使用を利用してシリアル化関数のコードを生成し、オプションの属性が設定されているかどうかをテストできます。

コード生成を行いたくない場合、属性のアクセサーを自動的に取得することはできません。ただし、シリアル化の側面は、たとえばすべての属性に共通の基本クラスを使用するなど、より古典的に処理できます。このようにして、それらをコンテナーに格納し、それらを反復処理して、それら自体をシリアル化するように依頼できます。

struct Attribute
{
    virtual QString serializePlainText() = 0;
};

// Generic attribute for the most common cases
template <typename T>
class MandatoryAttribute : public Attribute
{
    T value;

  public:

    std::string serializePlainText() 
    { 
        return boost::lexical_cast<std::string>(value);
    }
};

// Generic attribute for the most common cases
template <typename T>
class OptionalAttribute : public Attribute
{
    boost::optional<T> value;

  public:

    bool isSet() const { return value; }

    std::string serializePlainText() 
    { 
        return isSet() ? 
            boost::lexical_cast<std::string>(value) :
            "";
    }
};
于 2012-06-13T15:08:00.413 に答える
0

そう。私の最初の考えは「素敵な動的言語を使用する」ことですが、それは選択肢ではないと思います。ただし、漠然とした提案がいくつかあります。

  1. 地図を使用します。マップは繰り返し処理できるため、シリアライゼーション コードの負担が大幅に軽減されます。また、新しい属性を簡単に追加できます。
  2. 1 つの getter 関数と 1 つの setter 関数を使用します。これは、クライアント コードが呼び出す最も単純で最も適切な API であるためです。
  3. 既知の属性名とメタデータ値のマップを別々に保持します。

set を呼び出すと、要求された属性名を属性メタデータ テーブルで検索できます。その後、必要な検証または変換を実行できます。もちろん、ここでは各属性の構造に一連の一意で複雑な要件は必要ないと仮定しています...ほとんどの属性はおそらく最小または最大の長さなどを必要とするだけでしょうか?

シリアル化時に、属性メタデータ テーブルを反復処理して、すべての必須属性の名前を見つけ、属性値テーブルでそれらを検索できます。その後、属性値テーブルをトラバースし、残りの項目をシリアル化します。

于 2012-06-13T14:17:31.993 に答える