Аналог boost::any без использования RTTI
От: szag  
Дата: 29.07.10 08:05
Оценка:
Есть необходимость в создании контейнера, который может в себе хранить разнотипные данные (но с общим интерфейсом), но с уникальными ID. Так же, этот контейнер должен уметь хранить в себе такие же контейнеры, как он сам.
На первый взгляд сразу приходит в голову следующая конструкция
std::map<long, boost::any>

Однако, RTTI в проекте не включен и в идеале, включать его не хотелось бы.
Что требуется от контейнера:
1. добавить элемент
2. найти и вернуть именно тот тип элемента, который был добавлен, если элемента нет или тип не соответствует то эксепшн.
3. Иметь функцию, которая ищет элемент заданного типа по ID, сначала внутри себя, а потом, если не нашел, внутри всех элементов, которые являются контейнерами
4. Удалять элемент по id

Итак, контейнер хранит внутри себя как одиночные типы, так и контейнеры одиночных типов, контейнеры, могут содержать внутри себя другие контейнеры и т.д.. Все одиночные типы имеют общий базовый класс, все контейнеры тоже имеют общий (но не как у одиночных элементов) базовый класс

Приведу пример:
class ISimpleElement
{
    //...
};

class SimpleElementA : public ISimpleElement
{
    //...
};

class SimpleElementB : public ISimpleElement
{
    //...
};

class IContainer
{
    //...
};

class Container1 : public IContainer
{
    // ...
};

class Container2 : public IContainer
{
    // ...
};


int main()
{
    Container1 cont1;
    Container2 cont2;
    cont1.add(0, new SimpleElementA);
    cont1.add(1, new SimpleElementB);
    SimpleElementA* A = cont1.get<SimpleElementA>(0); // возвращает указатель на элемент нужного типа
    SimpleElementA* A1 = cont1.get<SimpleElementA>(1); // кидает эксепшн, что типа не соответствует типу хранимого элемента
    SimpleElementA* A2 = cont1.get<SimpleElementA>(3); // кидает эксепшн, элемента с таким id нет в контейнере

    cont2.add(2, new SimpleElementA);
    cont2.add(3, new SimpleElementB);
    cont2.add(4, &cont1); // еще не определился что будут передаваться указатели, скорее всего будут смарт поинтеры, но сейчас это не важно - просто пример

    SimpleElementA* A3 = cont2.get<SimpleElementA>(0); // ищет сначала внутри себя, а потом в других контейнерах внутри себя, когда находит - возвращает, не находит - кидает эксепшн
    Container1* p_cont1 = cont2.get<Container1>(4); // возвращает контейнер
}


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

Всего разных типов простых элементов и контейнеров около 10 каждого. Это число будет расти, но сильно не вырастет. Поэтому хотелось бы решение, при котором не надо писать много кода при добавлении нового типа элемента.
boost::any rtti велосипед
Re: Аналог boost::any без использования RTTI
От: Sni4ok  
Дата: 29.07.10 08:09
Оценка:
Здравствуйте, szag, Вы писали:

S>Есть необходимость в создании контейнера, который может в себе хранить разнотипные данные (но с общим интерфейсом), но с уникальными ID. Так же, этот контейнер должен уметь хранить в себе такие же контейнеры, как он сам.

S>Однако, RTTI в проекте не включен и в идеале, включать его не хотелось бы.

если набор возможных типов определён на стадии компиляции, то boost::variant.
Re: Аналог boost::any без использования RTTI
От: Кодт Россия  
Дата: 29.07.10 16:58
Оценка:
Здравствуйте, szag, Вы писали:

S>Есть необходимость в создании контейнера, который может в себе хранить разнотипные данные (но с общим интерфейсом), но с уникальными ID. Так же, этот контейнер должен уметь хранить в себе такие же контейнеры, как он сам.


Рекурсивная структура делается примерно так
typedef ISimpleElement Leaf;
struct Container;

typedef shared_ptr<Leaf> LeafPtr;
typedef shared_ptr<Container> ContainerPtr;

struct Container
{
  typedef variant<LeafPtr, ContainerPtr> Node;
  typedef map<long, Node> Table;
  Table items;
};

Важно, чтобы узел (Node) мог принимать неполные типы. То есть, голые указатели, или shared_ptr'ы (которые можно параметризовать неполными типами).

S>Однако, RTTI в проекте не включен и в идеале, включать его не хотелось бы.

S>Что требуется от контейнера:
S>1. добавить элемент
S>2. найти и вернуть именно тот тип элемента, который был добавлен, если элемента нет или тип не соответствует то эксепшн.

Включить идентификацию типа в интерфейс. Может быть, что-то COM-образное (GUID, то есть). Надо подумать, можно ли припахать микрософтовские вкусности наподобие __uuidof.

S>3. Иметь функцию, которая ищет элемент заданного типа по ID, сначала внутри себя, а потом, если не нашел, внутри всех элементов, которые являются контейнерами


Ну, это несложно. Рекурсивно оббежать.
Только функция поиска должна отдавать нечто большее, чем сам по себе элемент. Пригодится...
pair<ContainerPtr, LeafPtr> findById(ContainerPtr root, long id)
// возвращает лист и его непосредственного хозяина
{
  Table::const_iterator it = root->items.find(id);
  if(it != root->items.end())
  {
    if(LeafPtr* leaf = it->get<LeafPtr>())
      return make_pair(root, *leaf);
    //иначе - под этим id'ом таится контейнер...
  }

  for(it = root->items.begin(); it != root->items.end(); ++it)
  {
    if(ContainerPtr* cont = it->get<ContainerPtr>()) // нас интересуют только контейнеры
    {
      pair<ContainerPtr, LeafPtr> found = findById(*cont, id);
      if(found.first)
        return found;
    }
  }

  return make_pair(ContainerPtr(),LeafPtr());
}


S>4. Удалять элемент по id


Пригодилось!
pair<ContainerPtr, LeafPtr> found = findById(*cont, id);
if(found.first)
  found.first->items.erase(id);


S>Итак, контейнер хранит внутри себя как одиночные типы, так и контейнеры одиночных типов, контейнеры, могут содержать внутри себя другие контейнеры и т.д.. Все одиночные типы имеют общий базовый класс, все контейнеры тоже имеют общий (но не как у одиночных элементов) базовый класс


Выше — класс контейнера один, а все функции сделаны внешними, для наглядности.
Переделать их в методы интерфейса контейнера (или реализовать через методы контейнера) — дело несложное.

S>Понятно, что нужно запретить добавлять контейнеры друг в друга и добавлять контейнеры, которые уже есть в иерархии,


Э... непонятно. Сам же говорил, что контейнер содержит элементы и/или контейнеры...
Или хочется, чтобы каждое дерево было исключительно гомогенным: списки в списках, векторы в векторах, грубо говоря?

S> но это уже другой вопрос, сейчас надо хотя бы понять как можно решить такую задачу и в каком направлении двигаться.


Если изначально запретить вложенность, проблема исчезает

S>Всего разных типов простых элементов и контейнеров около 10 каждого. Это число будет расти, но сильно не вырастет. Поэтому хотелось бы решение, при котором не надо писать много кода при добавлении нового типа элемента.


Как я уже сказал: добавить идентификацию типа в состав интерфейса.
А если удастся сделать базовый IElementOrContainer, от которого породить отдельно ISimpleElement и IContainer, так вообще сказка!

Я бы даже пошёл именно по этому пути, вместо упражнений с boost::variant.
Перекуём баги на фичи!
Re[2]: Аналог boost::any без использования RTTI
От: szag  
Дата: 30.07.10 05:37
Оценка:
Здравствуйте, Кодт, Вы писали:

S>>Однако, RTTI в проекте не включен и в идеале, включать его не хотелось бы.

S>>Что требуется от контейнера:
S>>1. добавить элемент
S>>2. найти и вернуть именно тот тип элемента, который был добавлен, если элемента нет или тип не соответствует то эксепшн.

К>Включить идентификацию типа в интерфейс. Может быть, что-то COM-образное (GUID, то есть). Надо подумать, можно ли припахать микрософтовские вкусности наподобие __uuidof.


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

S>>3. Иметь функцию, которая ищет элемент заданного типа по ID, сначала внутри себя, а потом, если не нашел, внутри всех элементов, которые являются контейнерами


S>>Итак, контейнер хранит внутри себя как одиночные типы, так и контейнеры одиночных типов, контейнеры, могут содержать внутри себя другие контейнеры и т.д.. Все одиночные типы имеют общий базовый класс, все контейнеры тоже имеют общий (но не как у одиночных элементов) базовый класс


К>Выше — класс контейнера один, а все функции сделаны внешними, для наглядности.

К>Переделать их в методы интерфейса контейнера (или реализовать через методы контейнера) — дело несложное.

S>>Понятно, что нужно запретить добавлять контейнеры друг в друга и добавлять контейнеры, которые уже есть в иерархии,


К>Э... непонятно. Сам же говорил, что контейнер содержит элементы и/или контейнеры...

К>Или хочется, чтобы каждое дерево было исключительно гомогенным: списки в списках, векторы в векторах, грубо говоря?

Я плохо объяснил тут. Нельзя чтобы один и тот же контейнер можно было добавить 2 раза. Нельзя чтобы контейнер 1 модно было добавить в контейнер 2 (пока все ок), а затем контейнер 2 добавить в контейнер 1. Здесь я говорю не про типы, а про сами объекты.

S>> но это уже другой вопрос, сейчас надо хотя бы понять как можно решить такую задачу и в каком направлении двигаться.


К>Если изначально запретить вложенность, проблема исчезает


S>>Всего разных типов простых элементов и контейнеров около 10 каждого. Это число будет расти, но сильно не вырастет. Поэтому хотелось бы решение, при котором не надо писать много кода при добавлении нового типа элемента.


К>Как я уже сказал: добавить идентификацию типа в состав интерфейса.

К>А если удастся сделать базовый IElementOrContainer, от которого породить отдельно ISimpleElement и IContainer, так вообще сказка!

Извините, здесь не совсем понял как нам поможет общий интерфейс IElementOrContainer. Или Вы имеете ввиду хранить все в одном мапе безо всяких boost::variant?

К>Я бы даже пошёл именно по этому пути, вместо упражнений с boost::variant.
Re[3]: Аналог boost::any без использования RTTI
От: Кодт Россия  
Дата: 30.07.10 08:50
Оценка:
Здравствуйте, szag, Вы писали:

К>>Включить идентификацию типа в интерфейс. Может быть, что-то COM-образное (GUID, то есть). Надо подумать, можно ли припахать микрософтовские вкусности наподобие __uuidof.


S>Сейчас так и сделано — идентификация типа включена в интерфейс, однако это не очень удобно.

S>Приходится доставать указатель из shared_ptr, а затем кастить его и возвращать разыменованный пользователю класса.

Хотелось бы увидеть код. Подозреваю, что ты просто как-то неоптимально это делаешь.
К тому же, shared_ptr'ы прекрасно кастятся сами по себе.
shared_ptr<Base> pb;
....
if(pb->IsMyTypeFoo())
{
  shared_ptr<Foo> pf = static_pointer_cast<Foo>(pb);
  ....
}


Вообще же, даункастинг часто свидетельствует о проблемах в дизайне. Если не удалось вытащить нужную функциональность в интерфейс, абстрагировавшись от фактического типа объекта.

S>Хотя, другого выхода я пока не вижу. К сожалению, микросовтовские вкусности использовать нельзя, так как программа кросс-платформенная.


Ну, можно другие средства идентификации. Конечно, они все конвенциальные, т.е. требуют дисциплины программиста.
Самое простое — добавлять метод (макросом)
#define TYPE_IDENTITY(classname) \
   static  char const* static_type_id() { static const char id[]=PP_STR(classname); } \
   virtual char const* object_type_id() const { return static_type_id(); }

struct Interface
{
  .....
  TYPE_IDENTITY(Interface)
  .....
};

class Foo : public Interface
{
  .....
  TYPE_IDENTITY(Foo)
  .....
};

.....
Interface* p;
.....
if(p->object_type_id() == Foo::static_type_id()) // можно сравнивать прямо адреса строк, т.к. они уникальны
  Foo* f = static_cast<Foo*>(p);

Я здесь, конечно, предельно упростил.


S>Я плохо объяснил тут. Нельзя чтобы один и тот же контейнер можно было добавить 2 раза. Нельзя чтобы контейнер 1 модно было добавить в контейнер 2 (пока все ок), а затем контейнер 2 добавить в контейнер 1. Здесь я говорю не про типы, а про сами объекты.


Контейнер, будучи узлом дерева, может себе позволить — держать указатель на родительский узел.
(Единственно, там с подсчётом ссылок будут маленькие хитрости).
Или, хотя бы, булев признак "свободен/добавлен".

Добавлять куда-либо ранее добавленный контейнер запрещено. Вот и всё


S>Извините, здесь не совсем понял как нам поможет общий интерфейс IElementOrContainer. Или Вы имеете ввиду хранить все в одном мапе безо всяких boost::variant?


Ну да.
И первым делом проверять у элемента коллекции его тип — элемент это или контейнер. "А дальше как с гусём".
struct IElementOrContainer : enable_shared_from_this<IElementOrContainer>
{
  virtual shared_ptr<ISimpleElement> asElement() { return shared_ptr<ISimpleElement>(); }
  virtual shared_ptr<IContainer> asContainer()   { return shared_ptr<IContainer>();     }
};

struct ISimpleElement : IElementOrContainer
{
  shared_ptr<ISimpleElement> asElement() /*override*/ { return static_pointer_cast<ISimpleElement>(shared_from_this()); }
  .....
};

struct IContainer : IElementOrContainer
{
  shared_ptr<IContainer> asContainer() /*override*/ { return static_pointer_cast<IContainer>(shared_from_this()); }
  .....
};
Перекуём баги на фичи!
Re: Аналог boost::any без использования RTTI
От: Мишень-сан  
Дата: 30.07.10 10:57
Оценка:
Здравствуйте, szag, Вы писали:

...
  Скрытый текст
S>Есть необходимость в создании контейнера, который может в себе хранить разнотипные данные (но с общим интерфейсом), но с уникальными ID. Так же, этот контейнер должен уметь хранить в себе такие же контейнеры, как он сам.
S>На первый взгляд сразу приходит в голову следующая конструкция
S>
S>std::map<long, boost::any>
S>

S>Однако, RTTI в проекте не включен и в идеале, включать его не хотелось бы.
S>Что требуется от контейнера:
S>1. добавить элемент
S>2. найти и вернуть именно тот тип элемента, который был добавлен, если элемента нет или тип не соответствует то эксепшн.
S>3. Иметь функцию, которая ищет элемент заданного типа по ID, сначала внутри себя, а потом, если не нашел, внутри всех элементов, которые являются контейнерами
S>4. Удалять элемент по id

S>Итак, контейнер хранит внутри себя как одиночные типы, так и контейнеры одиночных типов, контейнеры, могут содержать внутри себя другие контейнеры и т.д.. Все одиночные типы имеют общий базовый класс, все контейнеры тоже имеют общий (но не как у одиночных элементов) базовый класс


S>Приведу пример:

S>
S>class ISimpleElement
S>{
S>    //...
S>};

S>class SimpleElementA : public ISimpleElement
S>{
S>    //...
S>};

S>class SimpleElementB : public ISimpleElement
S>{
S>    //...
S>};

S>class IContainer
S>{
S>    //...
S>};

S>class Container1 : public IContainer
S>{
S>    // ...
S>};

S>class Container2 : public IContainer
S>{
S>    // ...
S>};


S>int main()
S>{
S>    Container1 cont1;
S>    Container2 cont2;
S>    cont1.add(0, new SimpleElementA);
S>    cont1.add(1, new SimpleElementB);
S>    SimpleElementA* A = cont1.get<SimpleElementA>(0); // возвращает указатель на элемент нужного типа
S>    SimpleElementA* A1 = cont1.get<SimpleElementA>(1); // кидает эксепшн, что типа не соответствует типу хранимого элемента
S>    SimpleElementA* A2 = cont1.get<SimpleElementA>(3); // кидает эксепшн, элемента с таким id нет в контейнере

S>    cont2.add(2, new SimpleElementA);
S>    cont2.add(3, new SimpleElementB);
S>    cont2.add(4, &cont1); // еще не определился что будут передаваться указатели, скорее всего будут смарт поинтеры, но сейчас это не важно - просто пример

S>    SimpleElementA* A3 = cont2.get<SimpleElementA>(0); // ищет сначала внутри себя, а потом в других контейнерах внутри себя, когда находит - возвращает, не находит - кидает эксепшн
S>    Container1* p_cont1 = cont2.get<Container1>(4); // возвращает контейнер
S>}
S>


S>Понятно, что нужно запретить добавлять контейнеры друг в друга и добавлять контейнеры, которые уже есть в иерархии, но это уже другой вопрос, сейчас надо хотя бы понять как можно решить такую задачу и в каком направлении двигаться.


S>Всего разных типов простых элементов и контейнеров около 10 каждого. Это число будет расти, но сильно не вырастет. Поэтому хотелось бы решение, при котором не надо писать много кода при добавлении нового типа элемента.


Можно попробовать такой вариант:
struct Holder
{
  virtual unsigned long id() = 0;
};

template<class T>
struct TypeHolder : public Holder
{
  virtual unsigned long id()
  {
    static int idlocation;
    return (unsigned long)&idlocation;
  }
};


Для каждого обёрнутого типа будешь получать уникальный идентификатор. Получается RTTI своими руками.
Re[2]: Аналог boost::any без использования RTTI
От: Аноним  
Дата: 04.08.10 12:34
Оценка:
Здравствуйте, Мишень-сан, Вы писали:

МС>Можно попробовать такой вариант:

МС>
МС>struct Holder
МС>{
МС>  virtual unsigned long id() = 0;
МС>};

МС>template<class T>
МС>struct TypeHolder : public Holder
МС>{
МС>  virtual unsigned long id()
МС>  {
МС>    static int idlocation;
МС>    return (unsigned long)&idlocation;
МС>  }
МС>};
МС>


МС>Для каждого обёрнутого типа будешь получать уникальный идентификатор. Получается RTTI своими руками.


Со стандартом и оптимизирующим компилятором это дружит?
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.