Как я могу добавить отражение в приложение C ++? - PullRequest
228 голосов
/ 03 сентября 2008

Я хотел бы иметь возможность проанализировать класс C ++ на предмет его имени, содержимого (то есть членов и их типов) и т. Д. Я говорю здесь на родном C ++, а не на управляемом C ++, который имеет отражение. Я понимаю, что C ++ предоставляет некоторую ограниченную информацию, используя RTTI. Какие дополнительные библиотеки (или другие методы) могут предоставить эту информацию?

Ответы [ 32 ]

220 голосов
/ 01 августа 2012

Вам нужно, чтобы препроцессор генерировал данные отражения о полях. Эти данные могут быть сохранены как вложенные классы.

Во-первых, чтобы сделать запись в препроцессоре проще и понятнее, мы будем использовать типизированное выражение. Типизированное выражение - это просто выражение, которое помещает тип в круглые скобки. Таким образом, вместо написания int x вы будете писать (int) x. Вот несколько полезных макросов, которые помогут с печатными выражениями:

#define REM(...) __VA_ARGS__
#define EAT(...)

// Retrieve the type
#define TYPEOF(x) DETAIL_TYPEOF(DETAIL_TYPEOF_PROBE x,)
#define DETAIL_TYPEOF(...) DETAIL_TYPEOF_HEAD(__VA_ARGS__)
#define DETAIL_TYPEOF_HEAD(x, ...) REM x
#define DETAIL_TYPEOF_PROBE(...) (__VA_ARGS__),
// Strip off the type
#define STRIP(x) EAT x
// Show the type without parenthesis
#define PAIR(x) REM x

Далее мы определяем макрос REFLECTABLE для генерации данных о каждом поле (плюс само поле). Этот макрос будет называться так:

REFLECTABLE
(
    (const char *) name,
    (int) age
)

Таким образом, используя Boost.PP , мы перебираем каждый аргумент и генерируем данные следующим образом:

// A helper metafunction for adding const to a type
template<class M, class T>
struct make_const
{
    typedef T type;
};

template<class M, class T>
struct make_const<const M, T>
{
    typedef typename boost::add_const<T>::type type;
};


#define REFLECTABLE(...) \
static const int fields_n = BOOST_PP_VARIADIC_SIZE(__VA_ARGS__); \
friend struct reflector; \
template<int N, class Self> \
struct field_data {}; \
BOOST_PP_SEQ_FOR_EACH_I(REFLECT_EACH, data, BOOST_PP_VARIADIC_TO_SEQ(__VA_ARGS__))

#define REFLECT_EACH(r, data, i, x) \
PAIR(x); \
template<class Self> \
struct field_data<i, Self> \
{ \
    Self & self; \
    field_data(Self & self) : self(self) {} \
    \
    typename make_const<Self, TYPEOF(x)>::type & get() \
    { \
        return self.STRIP(x); \
    }\
    typename boost::add_const<TYPEOF(x)>::type & get() const \
    { \
        return self.STRIP(x); \
    }\
    const char * name() const \
    {\
        return BOOST_PP_STRINGIZE(STRIP(x)); \
    } \
}; \

Это создает постоянную fields_n, то есть количество отражаемых полей в классе. Затем он специализирует field_data для каждого поля. Он также дружит с классом reflector, так что он может получить доступ к полям, даже если они закрытые:

struct reflector
{
    //Get field_data at index N
    template<int N, class T>
    static typename T::template field_data<N, T> get_field_data(T& x)
    {
        return typename T::template field_data<N, T>(x);
    }

    // Get the number of fields
    template<class T>
    struct fields
    {
        static const int n = T::fields_n;
    };
};

Теперь, чтобы перебрать поля, мы используем шаблон посетителя. Мы создаем диапазон MPL от 0 до количества полей и получаем доступ к данным поля по этому индексу. Затем он передает данные поля предоставленному пользователем посетителю:

struct field_visitor
{
    template<class C, class Visitor, class I>
    void operator()(C& c, Visitor v, I)
    {
        v(reflector::get_field_data<I::value>(c));
    }
};


template<class C, class Visitor>
void visit_each(C & c, Visitor v)
{
    typedef boost::mpl::range_c<int,0,reflector::fields<C>::n> range;
    boost::mpl::for_each<range>(boost::bind<void>(field_visitor(), boost::ref(c), v, _1));
}

Теперь на момент истины мы собрали все это вместе. Вот как мы можем определить класс Person, который является отражаемым:

struct Person
{
    Person(const char *name, int age)
        :
        name(name),
        age(age)
    {
    }
private:
    REFLECTABLE
    (
        (const char *) name,
        (int) age
    )
};

Вот обобщенная функция print_fields, использующая данные отражения для итерации по полям:

struct print_visitor
{
    template<class FieldData>
    void operator()(FieldData f)
    {
        std::cout << f.name() << "=" << f.get() << std::endl;
    }
};

template<class T>
void print_fields(T & x)
{
    visit_each(x, print_visitor());
}

Пример использования print_fields с отражающим классом Person:

int main()
{
    Person p("Tom", 82);
    print_fields(p);
    return 0;
}

Какие выходы:

name=Tom
age=82

И вуаля, мы только что реализовали отражение в C ++, менее чем в 100 строках кода.

97 голосов
/ 24 ноября 2008

Есть два вида reflection плавания вокруг.

  1. Проверка путем итерации по элементам типа, перечисления его методов и т. Д.

    Это невозможно с C ++.
  2. Проверка путем проверки того, имеет ли тип класса (class, struct, union) метод или вложенный тип, получена из другого конкретного типа.

    Подобные вещи возможны в C ++ с использованием template-tricks. Используйте boost::type_traits для многих вещей (например, для проверки, является ли тип целочисленным). Для проверки существования функции-члена используйте Можно ли написать шаблон для проверки существования функции? . Для проверки наличия определенного вложенного типа используйте обычный SFINAE .

Если вы скорее ищете способы выполнения 1), например, посмотрите, сколько методов у класса, или хотите получить строковое представление идентификатора класса, то я боюсь, что нет стандартного C ++ способа сделать это. Вы должны использовать либо

  • Мета-компилятор, такой как Qt Meta Object Compiler, который переводит ваш код, добавляя дополнительную мета-информацию.
  • Платформа, состоящая из макросов, которые позволяют вам добавлять необходимые метаинформации. Вам нужно будет сообщить каркасу все методы, имена классов, базовые классы и все, что нужно.

C ++ сделан с учетом скорости. Если вам нужен высокоуровневый контроль, как в C # или Java, то, боюсь, я должен сказать вам, что без некоторых усилий нет пути.

55 голосов
/ 03 сентября 2008

И я бы хотел пони, но пони не бесплатны. : -Р

http://en.wikibooks.org/wiki/C%2B%2B_Programming/RTTI - это то, что вы собираетесь получить. Отражение, о котором вы думаете - полностью описательные метаданные, доступные во время выполнения - просто не существует для C ++ по умолчанию.

37 голосов
/ 19 августа 2009

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

class __declspec(export) MyClass
{
public:
    void Foo(float x);
}

Это заставляет компилятор встраивать данные определения класса в DLL / Exe. Но это не тот формат, который вы можете легко использовать для размышлений.

В моей компании мы создали библиотеку, которая интерпретирует эти метаданные и позволяет вам отражать класс, не вставляя дополнительные макросы и т. Д. В сам класс. Это позволяет вызывать функции следующим образом:

MyClass *instance_ptr=new MyClass;
GetClass("MyClass")->GetFunction("Foo")->Invoke(instance_ptr,1.331);

Это эффективно делает:

instance_ptr->Foo(1.331);

Функция Invoke (this_pointer, ...) имеет переменные аргументы. Очевидно, что вызывая функцию таким образом, вы обходите такие вещи, как const-safety и т. Д., Поэтому эти аспекты реализуются как проверки во время выполнения.

Я уверен, что синтаксис можно улучшить, и пока он работает только на Win32 и Win64. Мы обнаружили, что это действительно полезно, если у вас есть автоматические интерфейсы GUI для классов, создание свойств в C ++, потоковая передача в и из XML и т. Д., И нет необходимости выводить из определенного базового класса. Если спроса достаточно, мы могли бы привести его в форму для выпуска.

37 голосов
/ 03 сентября 2008

RTTI не существует для C ++.

Это просто неправильно. На самом деле, сам термин «RTTI» был придуман стандартом C ++. С другой стороны, RTTI не идет слишком далеко в реализации рефлексии.

14 голосов
/ 03 сентября 2008

Вам нужно посмотреть, что вы пытаетесь сделать, и будет ли RTTI удовлетворять вашим требованиям. Я реализовал свое собственное псевдоотражение для некоторых очень специфических целей. Например, я однажды хотел иметь возможность гибко настраивать то, что будет выводить симуляция. Требуется добавить некоторый шаблонный код к классам, которые будут выводиться:

namespace {
  static bool b2 = Filter::Filterable<const MyObj>::Register("MyObject");
} 

bool MyObj::BuildMap()
{
  Filterable<const OutputDisease>::AddAccess("time", &MyObj::time);
  Filterable<const OutputDisease>::AddAccess("person", &MyObj::id);
  return true;
}

Первый вызов добавляет этот объект в систему фильтрации, которая вызывает метод BuildMap(), чтобы выяснить, какие методы доступны.

Затем в конфигурационном файле вы можете сделать что-то вроде этого:

FILTER-OUTPUT-OBJECT   MyObject
FILTER-OUTPUT-FILENAME file.txt
FILTER-CLAUSE-1        person == 1773
FILTER-CLAUSE-2        time > 2000

Посредством некоторой магии шаблона, включающей boost, это преобразуется в серию вызовов методов во время выполнения (когда читается файл конфигурации), поэтому это довольно эффективно. Я бы не советовал делать это, если вам действительно не нужно, но когда вы это делаете, вы можете делать действительно классные вещи.

13 голосов
/ 03 сентября 2008

Я бы рекомендовал использовать Qt .

Существует лицензия с открытым исходным кодом, а также коммерческая лицензия.

13 голосов
/ 03 сентября 2008

Что вы пытаетесь делать с отражением?
Вы можете использовать библиотеки Boost type traits и typeof как ограниченную форму отражения во время компиляции. То есть вы можете проверять и изменять основные свойства типа, передаваемого в шаблон.

12 голосов
/ 20 июня 2010

РЕДАКТИРОВАТЬ : CAMP больше не поддерживается; доступны две вилки:

  • Один также называется CAMP и основан на том же API.
  • Ponder - это частичное переписывание, и оно должно быть предпочтительным, так как не требует Boost; он использует C ++ 11.

CAMP - это лицензированная библиотека MIT (ранее LGPL), которая добавляет отражение в язык C ++. Он не требует определенного этапа предварительной обработки при компиляции, но привязка должна быть выполнена вручную.

Текущая библиотека Tegesoft использует Boost, но есть также вилка , использующая C ++ 11, для которой больше не требуется Boost .

10 голосов
/ 28 ноября 2008

Я однажды сделал то, что вам нужно, и хотя возможно получить некоторый уровень отражения и доступа к высокоуровневым функциям, головная боль от обслуживания может не стоить этого. Моя система использовалась для полного отделения классов пользовательского интерфейса от бизнес-логики посредством делегирования, схожего с концепцией Objective-C передачи и пересылки сообщений. Способ сделать это состоит в том, чтобы создать некоторый базовый класс, способный отображать символы (я использовал пул строк, но вы могли бы сделать это с помощью перечислений, если вы предпочитаете скорость обработки ошибок во время компиляции, а не полную гибкость) функциям-указателям (на самом деле это не так). просто указатели на функции, но что-то похожее на то, что имеет Boost с Boost.Function - к которому у меня не было доступа в то время). Вы можете сделать то же самое для переменных-членов, если у вас есть некоторый общий базовый класс, способный представлять любое значение. Вся система была безошибочным грабежом кодирования и делегирования ключей с несколькими побочными эффектами, которые, возможно, стоили огромного количества времени, необходимого для того, чтобы каждый класс, использующий систему, соответствовал всем своим методам и элементам законным вызовам. : 1) Любой класс может вызывать любой метод любого другого класса без необходимости включать заголовки или писать поддельные базовые классы, чтобы интерфейс мог быть предопределен для компилятора; и 2) геттеры и сеттеры переменных-членов было легко сделать потокобезопасными, потому что изменение или доступ к их значениям всегда делался с помощью 2 методов в базовом классе всех объектов.

Это также привело к возможности делать некоторые действительно странные вещи, которые в противном случае были бы непростыми в C ++. Например, я мог бы создать объект Array, который содержал произвольные элементы любого типа, включая самого себя, и динамически создавать новые массивы, передавая сообщение всем элементам массива и собирая возвращаемые значения (аналогично map в Lisp). Другой была реализация наблюдения значения ключа, благодаря которой я смог настроить пользовательский интерфейс для немедленного реагирования на изменения в членах внутренних классов вместо того, чтобы постоянно опрашивать данные или излишне перерисовывать отображение.

Возможно, более интересным для вас является тот факт, что вы также можете сбросить все методы и члены, определенные для класса, и в строковой форме, не менее.

Недостатки системы, которые могут отговорить вас от беспокойства: добавление всех сообщений и значений ключей чрезвычайно утомительно; это медленнее, чем без каких-либо размышлений; вы будете ненавидеть видеть boost::static_pointer_cast и boost::dynamic_pointer_cast по всей вашей кодовой базе с яростной страстью; ограничения строго типизированной системы все еще существуют, вы просто их немного скрываете, так что это не так очевидно. Опечатки в ваших строках также не являются забавным или легко обнаруживаемым сюрпризом.

Что касается того, как реализовать что-то вроде этого: просто используйте общие и слабые указатели на какую-то общую базу (моя была очень образно названа «Объектом») и производная для всех типов, которые вы хотите использовать. Я бы порекомендовал установить Boost.Function вместо того, чтобы делать это так, как я это делал, что было с какой-то нестандартной хренью и кучей уродливых макросов для переноса вызовов указателя функции. Поскольку все сопоставлено, проверка объектов - это просто итерация по всем ключам. Поскольку мои классы были практически максимально приближены к прямому грабежу Какао с использованием только C ++, если вы хотите что-то подобное, я бы предложил использовать документацию по Какао в качестве образца.

...