0

全体的な設計:それぞれが共通の書き込み専用インターフェイスとクラス固有の読み取り専用アクセサー関数を持つ型のメンバー変数Cを含む集約クラスがあります(実際にはそのような通常の名前はありません)。各メンバー型は、独自の独立した抽象化を形成し、私のプログラムの他の場所で使用されます。NM_i, i = 1 ... N update()[F]un_i(), [F] = any letter, i = 1 .. NM_i

集約クラスは、単一のトランザクションですべてのメンバーを更新する必要があるため、すべてのメンバー変数のメンバー関数をupdate()呼び出す独自の関数があります。update()

// building blocks M_i, i = 1 ... N

class M_1
{
public:
    // common write-only interface
    void update();

    // M_1 specific read-only interface
    int fun_1() const;
    // ...
    int fun_K() const;

private:
    // impl
};

// ...

class M_N
{
public:
    // common write-only interface
    void update();

    // M_N specific read-only interface
    int gun_1() const;
    // ...
    int gun_K() const;

private:
    // impl
};

// aggregate containing member variables M_i, i = 1 ... N
class C
{
public:
    // update all members in a single transaction
    void update() 
    {
        m1_.update();
        // ...
        mN_.update();
    }

    // read-only interface?? see below

private:
    M_1 m1_;
    // ...
    M_N mN_;
};

質問: 集約クラスのさまざまなメンバー変数のさまざまなメンバー関数にアクセスできますか? 私は3つの選択肢を考えることができます:

代替案 1 : すべてのメンバー変数のすべてのメンバー関数にN * Kデリゲートを書き込むKN

class C
{
    int fun_1() const { return m1_.fun_1(); }
    // ...
    int fun_K() const { return m1_.fun_K(); }

    // ...

    int gun_1() const { return mN_.gun_1(); }
    // ...
    int gun_K() const { return mN_.gun_K(); }

    // as before
};

int res = C.fun_5(); // call 5th member function of 1st member variable 

代替 2 : すべてのメンバー変数Nにアクセサーを書き込むN

class C
{
    M_1 const& m1() const { return m1_; }

    // ...

    M_N const& mN() const { return mN_; }

    // as before
};

int res = C.m1().fun_5(); // call 5th member function of 1st member variable

代替 31 :アクセサ テンプレートをすべてのNメンバー変数に書き込む

class C
{
public:
    enum { m1, /* ... */ mN };

    template<std::size_t I>
    auto get() const -> decltype(std::get<I>(data_)) 
    { 
        return std::get<I>(data_); 
    }

private:
    std::tuple<M_1, /* ... */ M_N> data_;
};

int res = C.get<m1>().fun_5(); // call 5th member function of 1st member variable

代替案 1 は、デメテルの法則に違反することを回避しますが、非常に多くの面倒なボイラー プレート コードが必要です (私のアプリケーションでは、N = 5ラッパーを委譲します)。代替案 2 はラッパーの数を削減しますが、呼び出しコードは少し見苦しく感じます。しかし、そのコードはすべて読み取り専用であり、変更は一貫した集計によってのみ発生する可能性があるため、代替案 2 が代替案 1 よりも望ましい (そして少なくとも安全である) という私の現在の意見です。その場合は、1 つのアクセサーしか使用せず、Alternative 2 と同じ安全性が保証されるため、Alternative 3 が最適な選択となるはずです。K = 315update()

質問: このタイプのコードに適したインターフェイスは何ですか?

4

4 に答える 4

1

もう1つの可能性は

int func(int i, int j); // i,j can be enums as well..

あなたはこれがあなたにとって理にかなっているかどうかを決める必要がありますが。内部に巨大なネストされたスイッチを作成する必要がありますが、インターフェイスはより単純です。

もちろん、このメソッドは、オブジェクトを配列に格納でき、すべてのメンバー関数がM_i型の共通インターフェイスの一部である場合に理想的です。

于 2013-02-18T09:11:22.430 に答える
1

私のコメントを答えに変えます。

代替 1 (N*K デリゲート) を使用する場合は、Boost.Preprocessorを使用して定型的な作業を行うことができます。

#include <boost/preprocessor.hpp>

// Define identifier names

#define FUNCTIONS (fun)(gun)(hun)

#define MEMBER_NAMES (m1_)(m2_)(m3_)

#define SUFFIXES (_1)(_2)(_3)


// Utility "data structure"
// Used to hand down state from iteration over functions to iteration over suffixes

#define WRAP_DATA(function, member) \
  (2, (function, member))

#define UNWRAP_DATA_FUNTION(data) \
  BOOST_PP_ARRAY_ELEM(0, data)

#define UNWRAP_DATA_MEMBER(data) \
  BOOST_PP_ARRAY_ELEM(1, data)


// Accessor-generating functionality

  // Convenience macro for generating the correct accessor name
#define CREATE_FUNCTION_NAME(data, suffix) \
  BOOST_PP_CAT(UNWRAP_DATA_FUNCTION(data), suffix)

  // Macro generating one accessor delegation
#define GENERATE_ACCESSOR(r, data, suffix) \
  int CREATE_FUNCTION_NAME(data, suffix) () const { return UNWRAP_DATA_MEMBER(data).CREATE_FUNCTION_NAME(data, suffix) (); }


// Generate accessors

class C
{

  // Execute GENERATE_ACCESSOR once for each element of SUFFIXES
#define BOOST_PP_LOCAL_MACRO(iter) \
  BOOST_PP_SEQ_FOR_EACH(GENERATE_ACCESSOR, WRAP_DATA(BOOST_PP_SEQ_ELEM(iter, FUNCTIONS), BOOST_PP_SEQ_ELEM(iter, MEMBER_NAMES)), SUFFIXES)

#define BOOST_PP_LOCAL_LIMITS (0, BOOST_PP_SEQ_SIZE(FUNCTIONS) - 1)

  // Execute BOOST_PP_LOCAL_MACRO once for each value within BOOST_PP_LOCAL_LIMITS
#include BOOST_PP_LOCAL_ITERATE()

// rest of class C here
// ...

};

動作ロジックをよりよく強調するために、擬似コードに変換します。

FUNCTIONS = {fun, gun, hun};
MEMBER_NAMES = {m1_, m2_, m3_};
SUFFIXES = {_1, _2, _3};

struct Data {
  auto function, member;
};

auto createFunctionName(data, suffix) {
  return data.function + suffix;
}

auto generateAccessor(data, suffix) {
  return "int " + createFunctionName(data, suffix) + "() const { return " + data.member + "." + createFunctionName(data, suffix) + "(); }";
}


class C
{

for (i = 0; i < sizeof(FUNCTIONS); ++i) {
  foreach (suffix in SUFFIXES) {
    generateAccessor(Data(FUNCTIONS[i], MEMBER_NAMES[i]), suffix);
  }
}

};
于 2013-02-18T10:30:49.227 に答える
1

呼び出しのコンパイル時間の解決を備えた最高のユーザーフレンドリーなコードを提供するソリューションは、テンプレートに依存する必要があります。

実際、fun(i,j)(実際にはfun<i,j>()) whereiがメンバー変数へjのインデックスであり、この変数のメンバー関数へのインデックスである場合は、マッピングを定義する必要があります。両方のマッピング。

  • メンバー変数のインデックスと変数自体の間の最初のマッピング。これは、メンバー変数のインデックスと変数の型の間のマッピングを意味します。

  • メンバー関数インデックスとメンバー関数自体の間の 2 番目のマッピング。ただし、このマッピングはインデックス付きメンバー変数の型に依存するため、組み合わせごとに定義する必要があります。このマッピングを定義せずに、完全にインデックス化されたソリューションをユーザーに提供することはできません。またはその逆: 呼び出し元が呼び出したい j 番目の関数の名前を知るために i 番目の変数の型を気にしたくない場合 (これは i の型に依存します) -th 変数)、マッピングを提供する必要があります。

int v = c.fun<i, j>()これにより、ユーザーは、i 番目の変数の型も、この i 番目の変数の j 番目の関数の名前も知らずに呼び出すことができます。

template <typename M, int K> int fun(M const & m) const;

template <> int fun<M_1, 1>(M_1 const & m) const { return m.fun_1(); }
template <> int fun<M_1, 2>(M_1 const & m) const { return m.fun_2(); }
template <> int fun<M_1, 3>(M_1 const & m) const { return m.fun_3(); }

template <> int fun<M_2, 1>(M_2 const & m) const { return m.fun_1(); }
template <> int fun<M_2, 2>(M_2 const & m) const { return m.fun_2(); }
template <> int fun<M_2, 3>(M_2 const & m) const { return m.fun_3(); }

...

class C
{
    // Define the specialized class type for every N
    template <int N> class Mi { typedef void M; };
    template <> class Mi<1> { typedef M_1 M; };
    template <> class Mi<2> { typedef M_2 M; };
    template <> class Mi<3> { typedef M_3 M; };

    // Define the function to get the member N
    template <int N> Mi<N>::M const & get_M() const;
    template <> Mi<1>::M const & get_M() { return m1; } const;
    template <> Mi<2>::M const & get_M() { return m2; } const;
    template <> Mi<3>::M const & get_M() { return m3; } const;

    // Define the member function to call member N, function K
    template <int N, int K>
    int fun() { return fun<Mi<N>::M, K>( get_M<N>(); }

};

ここで、ユーザーがiおよびjを実行時変数として呼び出すことができるようにする場合、これは適切な方法ではありません。とint fun(i, j)が多い関数を優先します。両方を持つことはできません。ifswitch

于 2013-02-18T09:20:34.037 に答える
1

更新動作を単一要素の機能から完全に分離します。すべての M_i クラスは、単に update メソッドを含む Updatable インターフェイスを実装する必要があります。

これにより、N アクセサーを (非 const) 更新可能なインターフェイスに安全に公開できます。

class Updatable{

public:
  virtual void update() = 0;
} ; 


class M_i : public Updatable{

public:
 void update();

};

集約クラス C を指定すると、次のことができます。

  • N アクセサーを const M_i クラスに公開する

  • 特定の M_i クラスの Updateable インターフェイスを要求します。この (非定数) 参照にアクセスすることで、M_i インスタンスのいずれかに安全に更新を発行できます。

  • デリゲート更新を直接呼び出します。

.

class C{
    public:
              /** Returns the updatable interface related to M_1 */
              Updatable& getM_1Updater(){ return M_1}

              /** Returns the const reference to M_1*/
              const M_1& getM_1() const { return M_1}

              /** delegates update to each contained element */
              void update(){

                  m1.update();
                  m2.update();


                  [...]
              }

            };
于 2013-02-18T09:58:59.967 に答える