0

总体设计:我有一个聚合类C,其中包含N类型的成员变量M_i, i = 1 ... N,每个变量都有一个通用的只写 update()接口以及特定于类的只读访问器函数[F]un_i(), [F] = any letter, i = 1 .. N(实际上它们没有这样的常规名称)。每个成员类型都M_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_;
};

问题:我是否可以访问聚合类中各种成员变量的各种成员函数?我可以想到三种选择

备选方案 1:将N * K委托写入所有成员变量K的所有成员函数N

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 

备选方案 2N :对所有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

备选方案 3:将1访问器模板写入所有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并且K = 3,因此15委托包装器)。备选方案 2 减少了包装器的数量,但调用代码对我来说有点难看。但是由于所有这些代码都是只读的,并且修改只能通过一致的聚合发生update(),所以我目前认为替代方案 2 比替代方案 1 更可取(并且至少是安全的)。如果是这种情况,那么更进一步,备选方案 3 应该是最佳选择,因为它只使用一个访问器并且具有与备选方案 2 相同的安全保证。

问题:这种代码的首选接口是什么?

4

4 回答 4

1

另一种可能性是

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

尽管您需要确定这对您是否有意义。你需要在里面写一个巨大的嵌套开关,但界面更简单。

如果您可以将对象存储在数组中,并且所有成员函数都是M_i类型的公共接口的一部分,则此方法当然是理想的。

于 2013-02-18T09:11:22.430 回答
1

为您提供具有编译时调用分辨率的最佳用户友好代码的解决方案必须依赖模板。

实际上,如果您希望能够fun(i,j)(实际上fun<i,j>())调用 wherei是成员变量j的索引和该变量的成员函数的索引,那么您必须定义映射。两种映射。

  • 首先是成员变量索引和变量本身之间的映射,即成员变量索引和变量类型之间的映射。

  • 成员函数索引和成员函数本身之间的第二个映射。但是,由于此映射取决于索引成员变量的类型,因此必须为每个组合定义它。如果不定义此映射,您将无法为用户提供完全索引的解决方案。或者反过来:如果您不想让调用者为第 i 个变量的类型而烦恼,以知道他要调用的第 j 个函数的名称是什么(这取决于 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>(); }

};

现在,如果您希望用户可以使用ij作为运行时变量进行调用,那么这不是要走的路。更喜欢一个int fun(i, j)有很多if和的函数switch。你不能两者兼得。

于 2013-02-18T09:20:34.037 回答
1

我会将更新行为与单个元素功能完全分开。所有 M_i 类都应该实现一个仅包含更新方法的可更新接口。

这允许您安全地将 N 个访问器公开给(非 const)可更新接口。

class Updatable{

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


class M_i : public Updatable{

public:
 void update();

};

给定聚合类 C,您可以:

  • 向 const M_i 类公开 N 访问器

  • 请求给定 M_i 类的可更新接口。通过访问这个(非常量)引用,您可以安全地向任何 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 回答
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 回答