0

オブジェクト「識別子」のリスト(「識別子」ごとに一意の値を持つ長い列挙リスト)があります。

enum Identifier {
  Enum0,  // an identifier for a bool value
  Enum1,  //  ... for a float value
  Enum2,  //  ... for an int value
  // etc.
};

これらの識別子に関連付けられたValueオブジェクトのコレクションを維持したいと思います。これらのValueオブジェクトには単一の値が含まれますが、この値は整数、浮動小数点、ブール値、またはその他の(単純な)型の場合があります。これは、システム内の一連の構成値を管理する場合に使用されます。後で、これらの値型を拡張して内部値の検証をサポートし、一部の値を他の値に関連付けることを計画しています。

ただし、これらの値に対する操作を一般的に記述したいので、これらの値クラスにテンプレートを使用したいと思います。継承を使用する場合は、BaseValueを使用し、BaseValueからIntValue、FloatValueなどを派生させます。代わりに、私は価値、価値などを持っています。

ただし、これらの各値へのアクセスメカニズムを単一のコレクションに格納することも必要です。1つのクラスですべてをインスタンス化し、コレクション内で維持する必要があります。継承を使用している場合は、BaseValueへのポインターのベクトルを使用できます。しかし、私はテンプレートを使用しているので、これらのクラスは互いに多形的に関連していません。

そこで、パラメーター化されていない(空の?)抽象基本クラスに基づいてそれらを作成することを考えました。

class BaseParameter {
};

template<typename T>
class Parameter : public BaseParameter {
 public:
  explicit Parameter(T val) : val_(val) {}
  void set(ParameterSource src) { val_ = extract<T>(src); }
  T get() { return val_; };
 private:
  T val_;
};

'set'メンバー関数は、特定の"to_type"関数によって'再解釈される'値のソースである"ParameterSource"を受け取ることに注意してください。これは私の制御できないAPI関数です-タイプが何を意味するのかを知っているので、タイプを自分で解釈する必要があります。以下に設定します。これがextractの機能です。float、int、boolなどのさまざまなTタイプに特化しています。

次に、次のようにそれらをstd::vectorに追加できます。

std::vector<BaseParameter *> vec(10);
vec[Enum0] = new Parameter<bool>(true);   // this is where I state that it's a 'bool'
vec[Enum1] = new Parameter<float>(0.5);   //  ... or a float ...
vec[Enum2] = new Parameter<int>(42);      //  ... or an int ...

私はおそらくunique_ptrを使用する必要があることを知っていますが、今のところ私はこれを機能させようとしています。これまでのところ、これはうまく機能しているようです。ただし、インスタンス化されたテンプレートの完全なタイプが実行時に保持されるかどうかわからないため、注意が必要です。

後で、任意の列挙値で'vec'にインデックスを付け、パラメーターを取得して、そのパラメーターでメンバー関数を呼び出します。

void set_via_source(Identifier id, ParameterSource source) {
  // if id is in range...
  vec[id]->set(source);
}

そして、これらの構成値を利用する(したがってタイプを知っている)他のコードは、次の方法でそれらにアクセスできます。

int foo = vec[Enum2]->get() * 7;

ほとんどの場合、これは機能しているように見えました。コンパイルします。説明できない奇妙なクラッシュがいくつかありましたが、それはデバッガーもクラッシュする傾向があります。しかし、基本クラス自体がパラメーター化されていないため、C ++がポイントされたオブジェクトの実際の型(パラメーター化された型を含む)を判別できるかどうかわからないため、私はそれを非常に疑っています。

残念ながら、基本クラスをパラメーター化すると、これらのValueクラス間の共通性がなくなり、単一のコンテナーに格納できるようになります。

boost :: anyを調べて、それが役立つかどうかを確認しましたが、この場合に適用されるかどうかはわかりません。

より高いレベルでは、私がやろうとしているのは、外部ソースからの構成アイテムの膨大なコレクションを(APIを介して)接続し、アイテムに応じて異なるタイプの値を提供し、それらをローカルに保存して、残りの部分がコードは、それらが単純なデータメンバーであるかのように簡単にアクセスできます。また、巨大なswitchステートメントを記述したくない(それが機能するため)。

これは型消去が私を助けるかもしれない何かですか?

4

1 に答える 1

1

コンパイル時に各列挙型に関連付けられている型がわかっている場合は、型boost::variant消去や継承の有無にかかわらず、これを「簡単に」行うことができます。(編集: 最初のソリューションは多くの C++11 機能を使用します。自動化は少ないが C++03 準拠のソリューションを最後に置きます。)

#include <string>
#include <vector>
#include <boost/variant.hpp>
#include <boost/variant/get.hpp>

// Here's how you define your enums, and what they represent:
enum class ParameterId {
  is_elephant = 0,
  caloric_intake,
  legs,
  name,
  // ...
  count_
};
template<ParameterId> struct ConfigTraits;

// Definition of type of each enum
template<> struct ConfigTraits<ParameterId::is_elephant> {
  using type = bool;
};
template<> struct ConfigTraits<ParameterId::caloric_intake> {
  using type = double;
};
template<> struct ConfigTraits<ParameterId::legs> {
  using type = int;
};
template<> struct ConfigTraits<ParameterId::name> {
  using type = std::string;
};
// ...

// Here's the stuff that makes it work.

class Parameters {
  private:
    // Quick and dirty uniquifier, just to show that it's possible
    template<typename...T> struct TypeList {
      using variant = boost::variant<T...>;
    };

    template<typename TL, typename T> struct TypeListHas;
    template<typename Head, typename...Rest, typename T>
    struct TypeListHas<TypeList<Head, Rest...>, T>
        : TypeListHas<TypeList<Rest...>, T> {
    };
    template<typename Head, typename...Rest>
    struct TypeListHas<TypeList<Head, Rest...>, Head> {
      static const bool value = true;
    };
    template<typename T> struct TypeListHas<TypeList<>, T> {
      static const bool value = false;
    };

    template<typename TL, typename T, bool B> struct TypeListMaybeAdd;
    template<typename TL, typename T> struct TypeListMaybeAdd<TL, T, false> {
      using type = TL;
    };
    template<typename...Ts, typename T>
    struct TypeListMaybeAdd<TypeList<Ts...>, T, true> {
      using type = TypeList<Ts..., T>;
    };
    template<typename TL, typename T> struct TypeListAdd
        : TypeListMaybeAdd<TL, T, !TypeListHas<TL, T>::value> {
    };

    template<typename TL, int I> struct CollectTypes
        : CollectTypes<typename TypeListAdd<TL,
                                            typename ConfigTraits<ParameterId(I)>::type
                                           >::type, I - 1> {
    };
    template<typename TL> struct CollectTypes<TL, 0> {
      using type = typename TypeListAdd<TL,
                                        typename ConfigTraits<ParameterId(0)>::type
                                       >::type::variant;
    };

  public:
    using value_type =
        typename CollectTypes<TypeList<>, int(ParameterId::count_) - 1>::type;

    template<ParameterId pid>
    using param_type = typename ConfigTraits<pid>::type;

    // It would be better to not initialize all the values twice, but this
    // was easier.
    Parameters() : values_(size_t(ParameterId::count_)) {
       clear(std::integral_constant<int, int(ParameterId::count_) - 1>());
    }

    // getter for when you know the id at compile time. Should have better
    // error checking.
    template<ParameterId pid>
    typename ConfigTraits<pid>::type get() {
      // The following will segfault if the value has the wrong type.
      return *boost::get<typename ConfigTraits<pid>::type>(&values_[int(pid)]);
    }

    // setter when you know the id at compile time
    template<ParameterId pid>
    void set(typename ConfigTraits<pid>::type new_val) {
      values_[int(pid)] = new_val;
    }

    // getter for an id known only at runtime; returns a boost::variant;
    value_type get(ParameterId pid) {
      return values_[int(pid)];
    }

  private:
    // Initialize parameters to default values of the correct type
    template<int I> void clear(std::integral_constant<int, I>) {
       values_[I] = param_type<ParameterId(I)>();
       clear(std::integral_constant<int, I - 1>());
    }
    void clear(std::integral_constant<int, 0>) {
      values_[0] = param_type<ParameterId(0)>();
    }

    std::vector<value_type> values_;
};

// And finally, a little test
#include <iostream>
int main() {
  Parameters parms;
  std::cout << ('(' + parms.get<ParameterId::name>() + ')')<< ' '
            << parms.get<ParameterId::is_elephant>() << ' '
            << parms.get<ParameterId::caloric_intake>() << ' '
            << parms.get<ParameterId::legs>() << std::endl;
  parms.set<ParameterId::is_elephant>(true);
  parms.set<ParameterId::caloric_intake>(27183.25);
  parms.set<ParameterId::legs>(4);
  parms.set<ParameterId::name>("jumbo");
  std::cout << ('(' + parms.get<ParameterId::name>() + ')')<< ' '
            << parms.get<ParameterId::is_elephant>() << ' '
            << parms.get<ParameterId::caloric_intake>() << ' '
            << parms.get<ParameterId::legs>() << std::endl;

  return 0;
}

C++11 をまだ使用できない人のために、クラス以外の列挙型を使用しboost::variant、それ自体で型を構築するほどスマートではないバージョンを次に示します。そのため、手動で提供する必要があります。

#include <string>
#include <vector>
#include <boost/variant.hpp>
#include <boost/variant/get.hpp>

// Here's how you define your enums, and what they represent:
struct ParameterId {
  enum Id {
    is_elephant = 0,
    caloric_intake,
    legs,
    name,
    // ...
    count_
  };
};
template<int> struct ConfigTraits;

// Definition of type of each enum
template<> struct ConfigTraits<ParameterId::is_elephant> {
  typedef bool type;
};
template<> struct ConfigTraits<ParameterId::caloric_intake> {
  typedef double type;
};
template<> struct ConfigTraits<ParameterId::legs> {
  typedef int type;
};
template<> struct ConfigTraits<ParameterId::name> {
  typedef std::string type;
};
// ...

// Here's the stuff that makes it work.

// C++03 doesn't have integral_constant, so we need to roll our own:
template<int I> struct IntegralConstant { static const int value = I; };

template<typename VARIANT>
class Parameters {
  public:
    typedef VARIANT value_type;

    // It would be better to not initialize all the values twice, but this
    // was easier.
    Parameters() : values_(size_t(ParameterId::count_)) {
       clear(IntegralConstant<int(ParameterId::count_) - 1>());
    }

    // getter for when you know the id at compile time. Should have better
    // error checking.
    template<ParameterId::Id pid>
    typename ConfigTraits<pid>::type get() {
      // The following will segfault if the value has the wrong type.
      return *boost::get<typename ConfigTraits<pid>::type>(&values_[int(pid)]);
    }

    // setter when you know the id at compile time
    template<ParameterId::Id pid>
    void set(typename ConfigTraits<pid>::type new_val) {
      values_[int(pid)] = new_val;
    }

    // getter for an id known only at runtime; returns a boost::variant;
    value_type get(ParameterId::Id pid) {
      return values_[int(pid)];
    }

  private:
    // Initialize parameters to default values of the correct type
    template<int I> void clear(IntegralConstant<I>) {
      values_[I] = typename ConfigTraits<I>::type();
      clear(IntegralConstant<I - 1>());
    }
    void clear(IntegralConstant<0>) {
      values_[0] = typename ConfigTraits<0>::type();
    }

    std::vector<value_type> values_;
};

// And finally, a little test
#include <iostream>
int main() {
  Parameters<boost::variant<bool, int, double, std::string> > parms;
  std::cout << ('(' + parms.get<ParameterId::name>() + ')')<< ' '
            << parms.get<ParameterId::is_elephant>() << ' '
            << parms.get<ParameterId::caloric_intake>() << ' '
            << parms.get<ParameterId::legs>() << std::endl;
  parms.set<ParameterId::is_elephant>(true);
  parms.set<ParameterId::caloric_intake>(27183.25);
  parms.set<ParameterId::legs>(4);
  parms.set<ParameterId::name>("jumbo");
  std::cout << ('(' + parms.get<ParameterId::name>() + ')')<< ' '
            << parms.get<ParameterId::is_elephant>() << ' '
            << parms.get<ParameterId::caloric_intake>() << ' '
            << parms.get<ParameterId::legs>() << std::endl;

  return 0;
}
于 2013-02-07T06:34:33.317 に答える