Контейнер разнородных указателей

#c #templates

#c #шаблоны

Вопрос:

В настоящее время я использую перечисление для отображения в массив Base *. Каждому производному типу присваивается индекс с помощью перечисления.

 enum DerivedType {
    DERIVED_TYPE_1 = 0,
    DERIVED_TYPE_2,
    ...
    NUM_DERIVED_TYPES
};


class Base {

};


class Derived1 : public Base {
    static const DerivedType type;
};
const DerivedType Derived1::type = DERIVED_TYPE_1;


class Derived2 : public Base {
    static const DerivedType type;
};
const DerivedType Derived2::type = DERIVED_TYPE_2;


class Container {
    Base* obs[NUM_DERIVED_TYPES];

    template<class T>
    void addOb(T* ob) {
        obs[T::type] = ob;
    }

    template<class T>
    T* getOb() {
        return (T*) obs[T::type];
    }

    Base* getOb(DerivedType type) {
        return obs[type];
    }
};
  

Поскольку индекс каждого производного типа известен во время компиляции, есть ли способ заставить не-шаблон getOb(DerivedType type) возвращать правильный указатель DerivedN, возможно, путем поиска имени типа в сопоставлении int -> typename? Или есть лучший способ реализовать этот тип шаблона? Кроме того, было бы неплохо, чтобы каждый производный тип добавлял себя к любой структуре данных, присваивающей ему значение индекса.

В принципе, мне нужен статический контейнер разнородных указателей, к которому можно получить доступ по типу или индексу, при этом возвращая правильный производный * . Я предполагаю, что в Boost есть что-то, что могло бы сработать, но я пока этого не нашел.

Спасибо за любую помощь.

Ответ №1:

Хотя я не уверен на 100%, что правильно понимаю вопрос, вероятно, то, что вы упомянули (или аналогичный), может быть реализовано с помощью boost::fusion и boost::mpl .
Например:

 #include <boost/fusion/include/map.hpp>
#include <boost/fusion/include/at_key.hpp>
#include <boost/mpl/vector.hpp>
namespace bf = boost::fusion;
namespace bm = boost::mpl;

// This order has to match with the enumerators in DerivedType
typedef bm::vector< Derived1, Derived2 > DerivedTypes;

typedef bf::map< bf::pair< Derived1, Derived1* >
               , bf::pair< Derived2, Derived2* > > Container;

int main() {
    Container c( bf::make_pair< Derived1, Derived1* >(0)
               , bf::make_pair< Derived2, Derived2* >(0) );
    Derived1 d1;
    Derived2 d2;
    bf::at_key< Derived1 >( c ) = amp;d1; // access with type
    bf::at_key< Derived2 >( c ) = amp;d2;
    // access with enum
    bf::at_key< bm::at_c< DerivedTypes, DERIVED_TYPE_1 >::type >( c ) = amp;d1;
    bf::at_key< bm::at_c< DerivedTypes, DERIVED_TYPE_2 >::type >( c ) = amp;d2;
}
  

Комментарии:

1. Отлично, именно то, что я искал, спасибо. Одно из внесенных мной улучшений заключается в автоматическом заполнении карты из вектора: typedef bm::fold<DerivedTypes, bf::map<>, bm::push_back<bm::_1, bf::pair<bm::_2, boost::add_pointer<bm::_2> > > >::type Container;

Ответ №2:

(1) is there a way to have the "non-template" getOb(DerivedType type) return the correct DerivedN pointer

К сожалению, это невозможно. Для не шаблонных функций возвращаемый тип может быть только одного типа, и в вашем случае это должен быть Base* type. Текущая реализация Base* getOb(DerivedType type); является правильной.

(2) is there a better way to implement this type of pattern?

По крайней мере, вы можете облегчить свою работу, используя шаблонный промежуточный класс (без каких-либо накладных расходов), который инициализирует DerivedType переменную за вас. Например, объявите что-то вроде приведенного ниже:

 template<DerivedType TYPE>
struct Link : Base {
  static const DerivedType type;
};
template<DerivedType TYPE>
const DerivedType Link<TYPE>::type = TYPE;
  

Теперь Link<DerivedType> должны наследоваться производные классы, такие как:

 class Derived1 : public Link<DERIVED_TYPE_1> {
// no need to declare/define an explicit variable for DerivedType now.
};
  

Ответ №3:

Ну, то, что у вас сейчас есть, выглядит для меня довольно солидно. Единственная проблема, которую я вижу, заключается в том, что у вас будет только Base* доступный во время выполнения, поскольку одна функция может возвращать только 1 тип. Чтобы разрешить возврат нескольких типов без необходимости указывать дополнительный параметр, вы могли бы подделать функцию следующим образом:

 // if employed as a free function
class getOb{
  DerivedType _type;
public:
  getOb(DerivedType type)
    : _type(type) {}

  template<class T>
  operator T*() const{
    Base* ptr;
    // fetch correct pointer from wherever
    // using _type, if non-existant use 0
    return (T*) ptr;
  }
};
  

Использование, подобное

 Derived1* pd = getOb(DERIVED_TYPE_1);
assert(pd != 0);
  

Параметр передается в конструкторе, в то время как фактическое тело функции находится в операторе преобразования. Конечно, это хорошо выглядит только как самостоятельная функция, поэтому вот как это выглядело бы, если бы вы хотели реализовать это как функцию-член

 class GetObClass{
  mutable DerivedType _type;
public:
  GetObClassamp; operator()(DerivedType type) const{
    _type = type;
    return *this;
  }

  // template conversion operator as before
};
  

Использование, подобное

 class Container{
public:
  const GetObClass getOb;
};

Container c;
Derived1* pd = c.getOb(DERIVED_TYPE_1);
assert(pd != 0);