Каков подходящий интерфейс для работы с мета-аспектами классов? - PullRequest
2 голосов
/ 24 января 2012

Я ищу несколько советов о том, что было бы подходящим интерфейсом для работы с аспектами о классах (которые имеют дело с классами), но которые не являются частью реального класса, с которым они имеют дело (мета-аспекты).Это требует некоторого объяснения ...

В моем конкретном примере мне нужно реализовать собственную систему RTTI, которая немного сложнее, чем та, которая предлагается в C ++ (я не буду вдаваться в подробности, зачем мне это нужно).Мой базовый объект - FooBase, и каждый дочерний класс этой базы связан с FooTypeInfo объектом.

// Given a base pointer that holds a derived type,
// I need to be able to find the actual type of the
// derived object I'm holding.
FooBase* base = new FooDerived;

// The obvious approach is to use virtual functions...
const FooTypeInfo& info = base->typeinfo();

Использование виртуальных функций для работы с типом объекта во время выполнения кажется неправильныммне.Я склонен думать о типе объекта во время выполнения как о чем-то, что выходит за рамки класса и как таковое не должно быть частью его явного интерфейса.Следующий интерфейс заставляет меня чувствовать себя намного комфортнее ...

FooBase* base = new FooDerived;
const FooTypeInfo& info = foo::typeinfo(base);

Однако, хотя интерфейс не является частью класса, реализация все равно должна будет использовать виртуальные функции, чтобы эторабота:

class FooBase
{
   protected:
     virtual const FooTypeInfo& typeinfo() const = 0;
     friend const FooTypeInfo& ::foo::typeinfo(const FooBase*);
};

namespace foo
{
    const FooTypeInfo& typeinfo(const FooBase* ptr) {
        return ptr->typeinfo();
    }
}

Как вы думаете, я должен использовать этот второй интерфейс (который кажется мне более подходящим) и иметь дело с немного более сложной реализацией, или я просто выберу первый интерфейс?


@ Сет Карнеги

Это сложная проблема, если вы даже не хотите, чтобы производные классы знали о том, что они являются частью RTTI ... потому чтовы действительно не можете ничего сделать в конструкторе FooBase, который зависит от типа времени исполнения создаваемого класса (по той же причине, по которой вы не можете вызывать виртуальные методы в ctor или dtor).

FooBase является общей основой иерархии.У меня также есть отдельный шаблон класса CppFoo<>, который уменьшает количество шаблонов и упрощает определение типов.Есть еще один класс PythonFoo, который работает с производными от Python объектами.

template<typename FooClass>
class CppFoo : public FooBase
{
  private:
    const FooTypeInfo& typeinfo() const {
        return ::foo::typeinfo<FooClass>();
    }
};

class SpecificFoo : public CppFoo<SpecificFoo>
{
    // The class can now be implemented agnostic of the
    // RTTI system that works behind the scenes.
};

Еще несколько подробностей о том, как работает система, можно найти здесь:
https://stackoverflow.com/a/8979111/627005

1 Ответ

0 голосов
/ 25 января 2012

Вы можете связать динамический тип со статическим типом с помощью ключевого слова typeid и использовать возвращенные std::type_info объекты в качестве средства идентификации.Кроме того, если вы примените typeid к отдельному классу, созданному специально для этой цели, это будет совершенно не навязчиво для классов, в которых вы заинтересованы, хотя их имена еще должны быть известны заранее.Важно, что typeid применяется к типу, который поддерживает динамический полиморфизм - он должен иметь некоторую функцию virtual.

Вот пример:

#include <typeinfo>
#include <cstdio>

class Base;
class Derived;

template <typename T> class sensor { virtual ~sensor(); };

extern const std::type_info& base = typeid(sensor<Base>);
extern const std::type_info& derived = typeid(sensor<Derived>);

template <const std::type_info* Type> struct type
{
  static const char* name;

  static void stuff();
};

template <const std::type_info* Type> const char* type<Type>::name = Type->name();

template<> void type<&base>::stuff()
{
  std::puts("I know about Base");
}

template<> void type<&derived>::stuff()
{
  std::puts("I know about Derived");
}

int main()
{
  std::puts(type<&base>::name);
  type<&base>::stuff();

  std::puts(type<&derived>::name);
  type<&derived>::stuff();
}

Само собой разумеется,поскольку std::type_info являются собственными объектами, и они уникальны и упорядочены, вы можете управлять ими в коллекции и, таким образом, удалять тип, запрашиваемый из интерфейса:

template <typename T> struct sensor {virtual ~sensor() {}};

struct type
{
  const std::type_info& info;

  template <typename T>
  explicit type(sensor<T> t) : info(typeid(t))
  {};
};

bool operator<(const type& lh, const type& rh)
{
  return lh.info.before(rh.info);
}

int main()
{
  std::set<type> t;
  t.insert(type(sensor<Base>()));
  t.insert(type(sensor<Derived>()));

  for (std::set<type>::iterator i = t.begin(); i != t.end(); ++i)
    std::puts(i->info.name());
}

Конечно, вы можете смешивать и сочетать оба, так как высочтите нужным.

Два ограничения:

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

Одним из возможных вариантов является добавление RTTI-компонента "ловушка каркаса", такого как static const sensor<Myclass> rtti_MyClass;, к файлам реализации, где имена классов уже известны, и пусть конструктор сделает всю работу.Они также должны быть полными типами в этой точке, чтобы включить интроспекцию в датчике.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...