简体   繁体   English

C ++保留了一个指向模板对象的指针的集合,这些指针均来自非模板类

[英]C++ keeping a collection of pointers to template objects, all derived from a non-template class

I have a list of object "identifiers" (a long enumeration list, with a unique value per "identifier"): 我有一个对象“标识符”列表(一个很长的枚举列表,每个“标识符”都有唯一的值):

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

I wish to maintain a collection of Value objects associated with these identifiers. 我希望维护与这些标识符关联的Value对象的集合。 hese Value objects contain a single value, but this value may be integer, floating point, boolean or some other (simple) type. 这些Value对象包含一个值,但是该值可以是整数,浮点数,布尔值或其他(简单)类型。 This is in the context of managing a set of configuration values in a system. 这是在管理系统中的一组配置值的上下文中。 Later on I plan to extend these value types to support validation of the internal value, and relate some values to other values. 稍后,我计划扩展这些值类型以支持对内部值的验证,并将某些值与其他值相关联。

However I wish to use templates for these Value classes, because I want to write operations on these Values generically. 但是,我希望对这些Value类使用模板,因为我想对这些Values进行一般性的编写操作。 If I were to use inheritance I would have BaseValue, then derive IntValue, FloatValue, etc. from BaseValue. 如果要使用继承,我将拥有BaseValue,然后从BaseValue派生IntValue,FloatValue等。 Instead I have Value, Value, etc. 相反,我有价值,价值等。

But I also want to store an access mechanism to each of these Values in a single collection. 但我也想在单个集合中存储对每个值的访问机制。 I want one class to instantiate all of them and maintain them in the collection. 我希望一个类实例化所有实例并在集合中进行维护。 If I were using inheritance, I could use a vector of pointers to BaseValue. 如果使用继承,则可以使用指向BaseValue的指针向量。 But because I'm using templates, these classes are not polymorphically related to each other. 但是,因为我使用的是模板,所以这些类之间并不是多态相关的。

So I thought about making them based on an (empty?) abstract base class that is not parameterised: 因此,我考虑过基于参数化的(空?)抽象基类来制作它们:

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_;
};

Note that the 'set' member function takes a "ParameterSource", which is a source of a value that is 'reinterpreted' by specific "to_type" functions. 请注意,“ set”成员函数采用“ ParameterSource”,它是特定“ to_type”函数“重新解释”的值的源。 It's an API function out of my control - I have to interpret the type myself, given that I know what the type is meant to be, set below. 这是我无法控制的API函数-我必须自己解释类型,因为我知道下面要设置的类型。 That's what extract does - it's specialised for various T types like float, int, bool. 那就是提取物的作用-它专门用于float,int,bool等各种T类型。

Then I can add them to a std::vector like this: 然后我可以将它们添加到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 ...

I know I should probably use unique_ptr but for now I'm just trying to get this working. 我知道我应该使用unique_ptr,但是现在我只是想让它工作。 So far this seems to work fine. 到目前为止,这似乎工作正常。 But I'm wary of it because I'm not sure the full type of the instantiated templates is going to be retained at run-time. 但是我对此保持警惕,因为我不确定实例化模板的完整类型是否会在运行时保留。

Later I want to index the 'vec' by an arbitrary enum value, retrieve the parameter and call a member function on it: 稍后,我想通过任意枚举值为“ vec”建立索引,检索参数并对其调用成员函数:

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

And other code that makes use of these configuration values (and therefore knows the type) can access them with: 其他利用这些配置值(因此知道类型)的代码可以通过以下方式访问它们:

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

This seemed to work, most of the time. 在大多数情况下,这似乎可行。 It compiles. 它编译。 I've had some odd crashes I can't explain, that tend to crash the debugger too. 我遇到了一些我无法解释的奇怪的崩溃,这也会使调试器崩溃。 But I'm very suspicious of it, because I don't know whether C++ is able to determine the real type of the pointed-to object (including the parameterised type), because the base class isn't parameterised itself. 但是我对此非常怀疑,因为我不知道C ++是否能够确定指向对象的真实类型(包括参数化类型),因为基类本身没有参数化。

Unfortunately it seems to me that if I parameterise the base class, then I essentially remove the commonality between these Value classes that allow them to be stored in a single container. 不幸的是,在我看来,如果我对基类进行参数化,那么我实质上就删除了这些Value类之间的通用性,使它们可以存储在单个容器中。

I took a look at boost::any to see if that might help, but I'm not sure it would apply in this case. 我看了一下boost :: any来看看是否有帮助,但是我不确定在这种情况下是否适用。

At a higher level, what I'm trying to do is connect a vast collection of configuration items from an external source (via an API) that delivers values of different type depending on the item, and store them locally so that the rest of my code can easily access them as if they are simple data members. 在更高层次上,我想做的是从外部源(通过API)连接大量配置项,这些配置项根据项提供不同类型的值,并将其存储在本地,以便我的其余部分代码可以轻松地访问它们,就好像它们是简单的数据成员一样。 I also want to avoid writing a giant switch statement (because that would work). 我也想避免写一个巨大的switch语句(因为那样可以)。

Is this something that Type Erasure might help me with? 这对Type Erasure可能有帮助吗?

If you know at compile time the type associated with each enum, you can do this "easily" with boost::variant and without type-erasure or even inheritance. 如果您在编译时知道与每个枚举关联的类型,则可以使用boost::variant轻松进行此操作,而无需进行类型擦除甚至继承。 ( Edit : The first solution uses a lot of C++11 features. I put a less-automatic but C++03 conformant solution at the end.) 编辑 :第一个解决方案使用了许多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;
}

For the benefit of those who can't yet use C++11, here's a version which uses non-class enums and which is not smart enough to build the boost::variant type by itself, so you have to provide it manually: 为了使那些仍然不能使用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;
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM