Как я могу добавить отражение в приложение C ++?

263

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

Ник
источник
18
Не повезло, вы не можете сделать это без макросов и другой предварительной обработки, потому что необходимые метаданные не существуют, если вы не создадите их вручную с помощью некоторой магии предварительной обработки макросов.
Джалф
6
Информации, которую вы можете получить от RTTI, недостаточно для того, чтобы сделать то, о чем вы действительно хотели бы подумать. Например, вы не можете перебирать функции-члены класса.
Джозеф Гарвин

Ответы:

259

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

Во-первых, чтобы было проще и понятнее записать его в препроцессоре, мы будем использовать типизированное выражение. Типизированное выражение - это просто выражение, которое помещает тип в круглые скобки. Поэтому вместо того, чтобы писать, 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 строках кода.

Пол Фульц II
источник
106
Слава за то, что показал, как реализовать рефлексию, а не сказать, что это невозможно. Именно такие ответы делают ТАК большим ресурсом.
fearless_fool
4
Обратите внимание, что если вы попытаетесь скомпилировать это в Visual Studio, вы получите ошибку, потому что VS неправильно обрабатывает расширение макроса с переменным числом аргументов. Для VS, попробуйте добавить: #define DETAIL_TYPEOF_INT2(tuple) DETAIL_TYPEOF_HEAD tupleи #define DETAIL_TYPEOF_INT(...) DETAIL_TYPEOF_INT2((__VA_ARGS__)) и изменить определение TYPEOF (x) на:#define TYPEOF(x) DETAIL_TYPEOF_INT(DETAIL_TYPEOF_PROBE x,)
Phenglei Kai
Я получаю сообщение об ошибке "BOOST_PP_IIF_0" не называет тип. Не могли бы вы помочь.
Анкит Залани
3
Смотрите мой собственный ответ - stackoverflow.com/a/28399807/2338477 Я извлек и перепаковал все определения, и библиотека boost не нужна. В качестве демонстрационного кода я предоставляю сериализацию в XML и восстановление из XML.
TarmoPikaro
106

Есть два вида reflectionплавания.

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

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

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

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

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

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

Йоханнес Шауб - Литб
источник
122
C ++ сделан с расчетом на скорость, но философия не «настолько быстра, насколько это возможно», вместо этого «вы не платите за это, если не используете его». Я считаю, что язык может реализовать самоанализ таким образом, чтобы он соответствовал этой философии, а в C ++ его просто нет.
Джозеф Гарвин
8
@ Джозеф: Как это должно быть сделано? Это потребовало бы сохранения всех этих метаданных. Это означает, что вы должны заплатить за это, даже если вы не используете его. (Если вы не можете пометить отдельные типы как «поддерживающие рефлексию», но тогда мы почти опустились, где мы могли бы также использовать существующий
трюк с макросами
25
@jalf: Только метаданные, которые могут понадобиться. Если мы рассмотрим только отражение во время компиляции, это тривиально. Например, функция времени компиляции, members<T>которая возвращает список всех членов T. Если бы мы хотели иметь отражение во время выполнения (т.е. RTTI, смешанное с отражением), компилятор все равно будет знать все отраженные базовые типы. Вполне вероятно, members<T>(T&)что никогда не будет создан экземпляр для T = std :: string, поэтому нет необходимости включать RTTI для std :: string или его производных классов.
MSalters
9
Библиотека рефлексов (упоминается ниже) добавляет отражение в C ++, не замедляя существующий код по адресу: root.cern.ch/drupal/content/reflex
Джозеф Лизее
6
@Joe: отражение никогда не замедляет существующий код. Это только делает доставленный материал больше (так как вы должны предоставить базу данных типа информации ...).
мммммммм
56

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

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

Брэд Уилсон
источник
1
Я второй бред. Шаблоны C ++ могут быть довольно мощными, и имеется большой опыт работы с различными типами поведения «отражения», такими как «любая» библиотека, черты типа, C ++ RTTI и т. Д., Которые могут решить многие проблемы, для которых решается рефлексия. Итак, Ник, какова твоя цель здесь?
Аарон
7
Upvote для пони замечание! Я бы проголосовал дважды, поскольку ваш ответ тоже заслуживает этого, но, к сожалению, я получил только один, так что пони выигрывают. :-)
Франси Пенов
6
Я действительно не понимаю, почему это умный ответ. Я уже сказал, что хотел бы ссылки на библиотеки и т. Д. Для реализации этого. Отражение / самоанализ позволяет различным системам разрешать доступ к сценариям, сериализацию и т. Д.
Nick
3
@ Ник: Он уже ответил на это. Это невозможно, данные не существуют, и поэтому ни одна библиотека не сможет реализовать это за вас.
Джалф
@jalf Все еще странно для меня читать людей в мире программирования, говорящих как «это невозможно», а не «я не знаю как». Конечно, метаданные не существуют, но могут быть вставлены с помощью макросов
Freddx L.
38

Информация существует - но не в том формате, который вам нужен, и только если вы экспортируете свои классы. Это работает в 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. Мы обнаружили, что это действительно полезно, если у вас есть автоматические графические интерфейсы для классов, создание свойств в C ++, потоковая передача в и из XML и т. Д., И нет необходимости выводить из определенного базового класса. Если есть достаточно спроса, возможно, мы могли бы привести его в форму для выпуска.

Родерик
источник
1
Я думаю, что вы имеете в виду, __declspec(dllexport)и вы можете получить информацию из файла .map, если вы разрешите создание такого во время сборки.
Орвеллофил
19

Отражение не поддерживается C ++ из коробки. Это печально, потому что это делает оборонительные испытания болью.

Есть несколько подходов к рефлексии:

  1. использовать отладочную информацию (не переносимо).
  2. Обсыпайте ваш код макросами / шаблонами или другим исходным подходом (выглядит некрасиво)
  3. Измените компилятор, такой как clang / gcc, чтобы создать базу данных.
  4. Используйте подход Qt moc
  5. Boost Reflect
  6. Точное и плоское отражение

Первая ссылка выглядит наиболее многообещающе (использует мод для лязга), вторая обсуждает ряд приемов, третья - это другой подход с использованием gcc:

  1. http://www.donw.org/rfl/

  2. https://bitbucket.org/dwilliamson/clreflect

  3. https://root.cern.ch/how/how-use-reflex

В настоящее время существует рабочая группа для отражения C ++. Смотрите новости для C ++ 14 @ CERN:

Изменить 13/08/17:

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

  1. Статическое отражение в двух словах
  2. Статическое отражение
  3. Дизайн для статического отражения

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

Ниже приведена подробная информация о текущем состоянии на основе отзывов с последнего совещания по стандартам C ++:

Изменить 13/12/2017

Отражение, похоже, движется к C ++ 20 или, более вероятно, к TSR. Движение однако медленное.

Изменить 15/09/2018

Проект ТС был разослан в национальные органы для голосования.

Текст можно найти здесь: https://github.com/cplusplus/reflection-ts

Изменить 11/07/2019

Отражение TS полностью готово и доступно для комментариев и голосования в течение лета (2019).

Подход мета-шаблонного программирования должен быть заменен более простым подходом временного кода компиляции (не отраженным в TS).

Изменить 10/02/2020

Здесь есть запрос на поддержку TS отражения в Visual Studio:

Поговорим на TS автора Дэвида Санкеля:

Изменить 17 марта 2020 г.

Прогресс на размышление делается. Отчет «Отчет о поездке комитета ISO C ++ 2020-02 в Праге» можно найти здесь:

Подробности о том, что рассматривается для C ++ 23, можно найти здесь (включает короткий раздел по Reflection):

Изменить 4 июня 2020 года

Джефф Прешинг выпустил новый фреймворк под названием «Фанера», который содержит механизм для отражения во время выполнения. Более подробную информацию можно найти здесь:

Инструменты и подход выглядят наиболее полными и простыми в использовании.

Дамиан Диксон
источник
1
Ссылка на cern не работает.
Мостовский Свернуть
ссылки на cern должны быть исправлены сейчас. Они имеют тенденцию ломаться довольно часто, что является болью.
Дамиан Диксон
Этот ответ касается только отражения во время компиляции?
einpoklum
@einpoklum единственные текущие решения для отражения - это время компиляции, обычно с кодом мета-шаблона или макросами. Последний черновой вариант TS выглядит так, как будто он должен работать во время выполнения, но вам нужно было собрать все библиотеки с правильным компилятором для хранения необходимых метаданных.
Дамиан Диксон
@DamianDixon: Это не правда. Есть несколько библиотек отражений во время выполнения. Теперь, конечно, они довольно неуклюжи и либо согласны, либо требуют аннотации компилятора, но они все еще существуют. Если, как я понимаю ваш комментарий, вы ссылались только на рефлексию во время компиляции, отредактируйте ваш ответ, чтобы сделать его более понятным.
einpoklum
15

Вам нужно посмотреть, что вы пытаетесь сделать, и будет ли 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это преобразуется в серию вызовов методов во время выполнения (когда читается файл конфигурации), так что это довольно эффективно. Я бы не рекомендовал делать это, если вам действительно не нужно, но, когда вы это делаете, вы можете делать действительно классные вещи.

KeithB
источник
Должен любить эти функции, которые всегда возвращают true;) Я предполагаю, что это не зависит от статических проблем с порядком инициализации?
Пол
14

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

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

Жером
источник
1
Я посмотрел на это, но он использует макросы и исходный код нуждается в разборе для генерации кода метаданных. Я хотел бы избежать этого дополнительного шага. Я бы предпочел использовать библиотеку C ++ или простые макросы. Спасибо за идею, хотя.
Ник
10
QT или другая библиотека, реализующая подобный подход, - лучшее, что вы получите
jalf
5
Платите во время компиляции или платите во время выполнения - так, как вы платите!
Мартин Беккет
13

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

Ферруччо
источник
13

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

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

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

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

philant
источник
11

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

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

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

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

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

Мишель
источник
Привет, @ Майкл; у вас все еще есть исходный код для этого или вы от него избавились? Я хотел бы взглянуть на это, если вы не возражаете.
RandomDSdevel
Ой, пишется ваше имя неправильно! Не удивительно , что я никогда не получил ответ ...
RandomDSdevel
10

В C ++ есть еще одна новая библиотека для отражения, называемая RTTR (Run Time Type Reflection, см. Также github ).

Интерфейс похож на отражение в C # и работает без каких-либо RTTI.

Zack
источник
8

Два похожих на рефлексию решения, которые я знаю по моим дням в C ++:

1) Используйте RTTI, который обеспечит вам начальную загрузку для построения вашего поведения, похожего на отражение, если вы сможете получить все свои классы из базового класса «объект». Этот класс может предоставлять некоторые методы, такие как GetMethod, GetBaseClass и т. Д. Что касается работы этих методов, вам нужно будет вручную добавить несколько макросов для украшения ваших типов, которые за кулисами создают метаданные в типе, чтобы предоставить ответы на GetMethods и т. Д.

2) Другой вариант, если у вас есть доступ к объектам компилятора, это использовать DIA SDK . Если я правильно помню, это позволяет вам открывать pdbs, которая должна содержать метаданные для ваших типов C ++. Этого может быть достаточно, чтобы сделать то, что вам нужно. На этой странице показано, как вы можете получить все базовые типы класса, например.

Оба эти решения немного уродливы, хотя! Нет ничего лучше, чем немного C ++, чтобы вы могли оценить роскошь C #.

Удачи.

user4385
источник
Это хитрый и гигантский хак с DIA SDK, который вы там предложили.
Sqeaky
7

РЕДАКТИРОВАТЬ: Обновленная неработающая ссылка по состоянию на 7 февраля 2017 года.

Я думаю, что никто не упомянул это:

В CERN они используют систему полного отражения для C ++:

ЦЕРН Рефлекс . Кажется, работает очень хорошо.

Герман Диаго
источник
@ j4nbur53 Ссылка не работает, потому что кажется, что они достигли вехи: root.cern.ch
Герман Диаго
Может быть, вы имеете в виду эту ссылку root.cern.ch/root/doc/ROOTUsersGuideHTML/ch07.html Глава Reflex?
Мостовский Свернуть
Попробуйте это root.cern.ch/how/how-use-reflex . Reflex работает как генератор, который анализирует ваши заголовочные файлы и генерирует код / ​​библиотеку самоанализа c ++, на который вы можете ссылаться и использовать простой API.
Адам
6

Этот вопрос сейчас немного стар (не знаю, почему я продолжаю задавать старые вопросы сегодня), но я думал о BOOST_FUSION_ADAPT_STRUCT, который вводит отражение во время компиляции.

Конечно, вы должны отобразить это на отражение во время выполнения, и это будет не слишком легко, но это возможно в этом направлении, хотя это не будет наоборот :)

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

Матье М.
источник
2
от minghua (который изначально редактировал пост): я покопался в этом решении BOOST_FUSION_ADAPT_STRUCT и в итоге нашел пример. Посмотрите этот новый SO вопрос - C ++ переходит во вложенное поле struct с boost fusion adapt_struct .
Матье М.
Отлично, Матье! Просто понял, что видел твои намеки здесь и там в течение прошлого года. Не заметил, что они связаны до сих пор. Это было очень вдохновляющим.
Минхуа
6

Я думаю, что вы могли бы найти интересную статью Доминика Филиона "Использование шаблонов для отражения в C ++". Он находится в разделе 1.4 игры Gems 5 . К сожалению, у меня нет своей копии со мной, но ищите ее, потому что я думаю, что это объясняет то, что вы просите.

Луис
источник
4

Вдумайтесь - это библиотека отражений C ++, в ответ на этот вопрос. Я рассмотрел варианты и решил сделать свои собственные, так как я не мог найти тот, который отмечал бы все мои коробки.

Хотя на этот вопрос есть отличные ответы, я не хочу использовать тонны макросов или полагаться на Boost. Boost - отличная библиотека, но есть множество небольших проектов C ++ 0x, которые проще и быстрее компилируются. Существуют также преимущества возможности внешнего декорирования класса, например, оборачивание библиотеки C ++, которая (пока?) Не поддерживает C ++ 11. Это форк CAMP, использующий C ++ 11, который больше не требует Boost .

Ник
источник
4

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

Таким образом, C ++ не дает отражения, и его не так просто «смоделировать» как общее правило, как отмечали другие ответы.

В разделе «другие методы», если у вас нет языка с отражением, получите инструмент, который может извлекать нужную вам информацию во время компиляции.

Наш инструментарий реинжиниринга программного обеспечения DMS представляет собой обобщенную технологию компиляции, параметризованную явными определениями языка. У этого есть определения языка для C, C ++, Java, COBOL, PHP, ...

Для версий C, C ++, Java и COBOL он обеспечивает полный доступ к деревьям разбора и информации таблицы символов. Эта информация таблицы символов включает в себя тип данных, которые вы, вероятно, захотите получить от «отражения». Если ваша цель состоит в том, чтобы перечислить некоторый набор полей или методов и что- то с ними сделать, DMS можно использовать для преобразования кода в соответствии с тем, что вы найдете в таблицах символов произвольным образом.

Ира Бакстер
источник
3

Вы можете найти другую библиотеку здесь: http://www.garret.ru/cppreflection/docs/reflect.html. Она поддерживает 2 способа: получение информации о типе из отладочной информации и позволить программисту предоставить эту информацию.

Я также заинтересовался размышлениями для своего проекта и нашел эту библиотеку, я еще не пробовал ее, но попробовал другие инструменты этого парня, и мне нравится, как они работают :-)

alariq
источник
3

Проверьте Classdesc http://classdesc.sf.net . Он обеспечивает отражение в форме «дескрипторов» класса, работает с любым стандартным компилятором C ++ (да, известно, что он работает как с Visual Studio, так и с GCC) и не требует аннотации исходного кода (хотя существуют некоторые прагмы для обработки сложных ситуаций. ). Он разрабатывался более десяти лет и использовался в ряде проектов промышленного масштаба.

Рассел Стэндиш
источник
1
Добро пожаловать в переполнение стека. Хотя этот ответ по теме, важно указать, что вы являетесь автором этого программного обеспечения, чтобы прояснить, что это не беспристрастная рекомендация :-)
Мэтью Строубридж
2

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

В настоящее время я исследую, когда мне так хочется, методы, которые можно использовать для использования attribute_linearly, чтобы упростить определение отражаемых типов. Я довольно далеко продвинулся в этом деле, но у меня все еще есть пути. Изменения в C ++ 0x, скорее всего, окажут большую помощь в этой области.

Эдвард Стрендж
источник
2

Похоже, C ++ до сих пор не имеет этой функции. И C ++ 11 тоже отложил отражение ((

Ищите некоторые макросы или делайте собственные. Qt также может помочь с отражением (если это можно использовать).

Богдана
источник
2

даже если в c ++ отражение не поддерживается "из коробки", его не сложно реализовать. Я встречал эту замечательную статью: http://replicaisland.blogspot.co.il/2010/11/building-reflective-object-system-in-c.html

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

Нижняя строка - отражение может окупиться, если все сделано правильно, и это полностью выполнимо в C ++.

Наоре Азенкут
источник
2

Я хотел бы рекламировать существование автоматического инструментария самоанализа / отражения "IDK". Он использует мета-компилятор, такой как Qt, и добавляет метаинформацию непосредственно в объектные файлы. Утверждается, что он прост в использовании. Нет внешних зависимостей. Он даже позволяет автоматически отражать std :: string и затем использовать его в скриптах. Пожалуйста, посмотрите на ИДК

Евгений Г
источник
2

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

https://github.com/tapika/TestCppReflect/blob/master/MacroHelpers.h

набор определений плюс функциональность поверх него:

https://github.com/tapika/TestCppReflect/blob/master/CppReflect.h https://github.com/tapika/TestCppReflect/blob/master/CppReflect.cpp https://github.com/tapika/TestCppReflect/ блоб / ведущий / TypeTraits.h

Пример приложения также находится в репозитории git, здесь: https://github.com/tapika/TestCppReflect/

Я частично скопирую это здесь с объяснением:

#include "CppReflect.h"
using namespace std;


class Person
{
public:

    // Repack your code into REFLECTABLE macro, in (<C++ Type>) <Field name>
    // form , like this:

    REFLECTABLE( Person,
        (CString)   name,
        (int)       age,
...
    )
};

void main(void)
{
    Person p;
    p.name = L"Roger";
    p.age = 37;
...

    // And here you can convert your class contents into xml form:

    CStringW xml = ToXML( &p );
    CStringW errors;

    People ppl2;

    // And here you convert from xml back to class:

    FromXml( &ppl2, xml, errors );
    CStringA xml2 = ToXML( &ppl2 );
    printf( xml2 );

}

REFLECTABLEdefine использует имя класса + имя поля с offsetof-, чтобы определить, в каком месте в памяти находится конкретное поле. Я постарался максимально приблизиться к терминологии .NET, но C ++ и C # различны, так что это не 1 к 1. Вся модель отражения C ++ находится в классах TypeInfoи FieldInfoклассах.

Я использовал pugi xml parser для извлечения демонстрационного кода в xml и восстановления его из xml.

Таким образом, вывод, созданный демонстрационным кодом, выглядит следующим образом:

<?xml version="1.0" encoding="utf-8"?>
<People groupName="Group1">
    <people>
        <Person name="Roger" age="37" />
        <Person name="Alice" age="27" />
        <Person name="Cindy" age="17" />
    </people>
</People>

Также возможно включить любую поддержку класса / структуры третьей стороны через класс TypeTraits и частичную спецификацию шаблона - чтобы определить свой собственный класс TypeTraitsT, аналогично CString или int - см. Пример кода в

https://github.com/tapika/TestCppReflect/blob/master/TypeTraits.h#L195

Это решение применимо для Windows / Visual Studio. Можно портировать его на другие ОС / компиляторы, но этого еще не сделали. (Спросите меня, если вам действительно нравится решение, я мог бы помочь вам)

Это решение применимо для сериализации одним выстрелом одного класса с несколькими подклассами.

Однако, если вы ищете механизм для сериализации частей класса или даже для контроля над тем, что производят вызовы отражения, вы можете взглянуть на следующее решение:

https://github.com/tapika/cppscriptcore/tree/master/SolutionProjectModel

Более подробную информацию можно найти в видео YouTube:

Отражение типа среды выполнения C ++ https://youtu.be/TN8tJijkeFE

Я пытаюсь объяснить немного глубже о том, как будет работать отражение С ++.

Пример кода будет выглядеть, например, так:

https://github.com/tapika/cppscriptcore/blob/master/SolutionProjectModel/testCppApp.cpp

c.General.IntDir = LR"(obj\$(ProjectName)_$(Configuration)_$(Platform)\)";
c.General.OutDir = LR"(bin\$(Configuration)_$(Platform)\)";
c.General.UseDebugLibraries = true;
c.General.LinkIncremental = true;
c.CCpp.Optimization = optimization_Disabled;
c.Linker.System.SubSystem = subsystem_Console;
c.Linker.Debugging.GenerateDebugInformation = debuginfo_true;

Но каждый шаг здесь фактически приводит к вызову функции с использованием свойств C ++ с __declspec(property(get =, put ... ).

который получает полную информацию о типах данных C ++, именах свойств C ++ и указателях экземпляров классов в форме пути, и на основе этой информации вы можете генерировать xml, json или даже сериализовать эту информацию через Интернет.

Примеры таких виртуальных функций обратного вызова можно найти здесь:

https://github.com/tapika/cppscriptcore/blob/master/SolutionProjectModel/VCConfiguration.cpp

Смотрите функции ReflectCopyи виртуальные функции ::OnAfterSetProperty.

Но поскольку тема действительно продвинутая - рекомендую сначала проверить видео.

Если у вас есть идеи по улучшению, не стесняйтесь связаться со мной.

TarmoPikaro
источник
2

Библиотека отражений произвольного доступа обеспечивает довольно простое и интуитивно понятное отражение - вся информация о полях / типах предназначена для того, чтобы либо быть доступной в массивах, либо ощущаться как доступ к массиву. Он написан для C ++ 17 и работает с Visual Studios, g ++ и Clang. Библиотека имеет только заголовок, то есть вам нужно только скопировать «Reflect.h» в ваш проект, чтобы использовать ее.

Для отраженных структур или классов необходим макрос REFLECT, в котором вы указываете имя класса, который вы отображаете, и имена полей.

class FuelTank {
    public:
        float capacity;
        float currentLevel;
        float tickMarks[2];

    REFLECT(() FuelTank, () capacity, () currentLevel, () tickMarks)
};

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

Цикл по полям может быть так же просто, как ...

for ( size_t i=0; i<FuelTank::Class::TotalFields; i++ )
    std::cout << FuelTank::Class::Fields[i].name << std::endl;

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

FuelTank::Class::ForEachField(fuelTank, [&](auto & field, auto & value) {
    using Type = typename std::remove_reference<decltype(value)>::type;
    std::cout << TypeToStr<Type>() << " " << field.name << ": " << value << std::endl;
});

JSON библиотека построена на вершине RandomAccessReflection , которая автоматически идентифицирует соответствующие выходные JSON - представления для чтения или записи, и может рекурсивно пройти любые отраженные поля, а также массивы и STL контейнеры.

struct MyOtherObject { int myOtherInt; REFLECT(() MyOtherObject, () myOtherInt) };
struct MyObject
{
    int myInt;
    std::string myString;
    MyOtherObject myOtherObject;
    std::vector<int> myIntCollection;

    REFLECT(() MyObject, () myInt, () myString, (Reflected) myOtherObject, () myIntCollection)
};

int main()
{
    MyObject myObject = {};
    std::cout << "Enter MyObject:" << std::endl;
    std::cin >> Json::in(myObject);
    std::cout << std::endl << std::endl << "You entered:" << std::endl;
    std::cout << Json::pretty(myObject);
}

Вышесказанное можно запустить так ...

Enter MyObject:
{
  "myInt": 1337, "myString": "stringy", "myIntCollection": [2,4,6],
  "myOtherObject": {
    "myOtherInt": 9001
  }
}


You entered:
{
  "myInt": 1337,
  "myString": "stringy",
  "myOtherObject": {
    "myOtherInt": 9001
  },
  "myIntCollection": [ 2, 4, 6 ]
}

Смотрите также...

jjf28
источник
1

Отражение в C ++ очень полезно, в тех случаях, когда вам нужно запустить какой-то метод для каждого члена (например: сериализация, хеширование, сравнение). Я пришел с общим решением, с очень простым синтаксисом:

struct S1
{
    ENUMERATE_MEMBERS(str,i);
    std::string str;
    int i;
};
struct S2
{
    ENUMERATE_MEMBERS(s1,i2);
    S1 s1;
    int i2;
};

Где ENUMERATE_MEMBERS - это макрос, который описан позже (UPDATE):

Предположим, мы определили функцию сериализации для int и std :: string следующим образом:

void EnumerateWith(BinaryWriter & writer, int val)
{
    //store integer
    writer.WriteBuffer(&val, sizeof(int));
}
void EnumerateWith(BinaryWriter & writer, std::string val)
{
    //store string
    writer.WriteBuffer(val.c_str(), val.size());
}

И у нас есть универсальная функция рядом с «секретным макросом»;)

template<typename TWriter, typename T>
auto EnumerateWith(TWriter && writer, T && val) -> is_enumerable_t<T>
{
    val.EnumerateWith(write); //method generated by ENUMERATE_MEMBERS macro
}

Теперь вы можете написать

S1 s1;
S2 s2;
//....
BinaryWriter writer("serialized.bin");

EnumerateWith(writer, s1); //this will call EnumerateWith for all members of S1
EnumerateWith(writer, s2); //this will call EnumerateWith for all members of S2 and S2::s1 (recursively)

Таким образом, имея макрос ENUMERATE_MEMBERS в определении структуры, вы можете создавать сериализацию, сравнение, хэширование и другие элементы, не касаясь исходного типа, единственное требование - реализовать метод «EnumerateWith» для каждого типа, который не является перечисляемым, для каждого перечислителя (например, BinaryWriter) , Обычно вам нужно реализовать 10-20 «простых» типов для поддержки любого типа в вашем проекте.

Этот макрос должен иметь нулевые накладные расходы для структурирования создания / уничтожения во время выполнения, и код T.EnumerateWith () должен генерироваться по требованию, что может быть достигнуто с помощью функции встроенного шаблона, поэтому единственные накладные расходы в Все дело в том, чтобы добавить ENUMERATE_MEMBERS (m1, m2, m3 ...) к каждой структуре, в то время как реализация конкретного метода для каждого типа члена является обязательной в любом решении, поэтому я не принимаю это как накладные расходы.

ОБНОВЛЕНИЕ: Существует очень простая реализация макроса ENUMERATE_MEMBERS (однако его можно немного расширить для поддержки наследования из перечисляемой структуры)

#define ENUMERATE_MEMBERS(...) \
template<typename TEnumerator> inline void EnumerateWith(TEnumerator & enumerator) const { EnumerateWithHelper(enumerator, __VA_ARGS__ ); }\
template<typename TEnumerator> inline void EnumerateWith(TEnumerator & enumerator) { EnumerateWithHelper(enumerator, __VA_ARGS__); }

// EnumerateWithHelper
template<typename TEnumerator, typename ...T> inline void EnumerateWithHelper(TEnumerator & enumerator, T &...v) 
{ 
    int x[] = { (EnumerateWith(enumerator, v), 1)... }; 
}

// Generic EnumerateWith
template<typename TEnumerator, typename T>
auto EnumerateWith(TEnumerator & enumerator, T & val) -> std::void_t<decltype(val.EnumerateWith(enumerator))>
{
    val.EnumerateWith(enumerator);
}

И вам не нужна никакая сторонняя библиотека для этих 15 строк кода;)

jenkas
источник
1

С помощью BOOST_HANA_DEFINE_STRUCT из библиотеки Boost :: Hana вы можете получить интересные функции статического отражения для конструкций .
Hana достаточно универсален не только для того сценария, который вы имеете в виду, но и для большого количества метапрограммирования шаблонов.

nnolte
источник
0

Если вы объявите указатель на функцию, подобную этой:

int (*func)(int a, int b);

Вы можете назначить место в памяти для этой функции следующим образом (требуется libdlи dlopen)

#include <dlfcn.h>

int main(void)
{
    void *handle;
    char *func_name = "bla_bla_bla";
    handle = dlopen("foo.so", RTLD_LAZY);
    *(void **)(&func) = dlsym(handle, func_name);
    return func(1,2);
}

Чтобы загрузить локальный символ с помощью косвенного обращения, вы можете использовать dlopenв вызывающем двоичном ( argv[0]).

Единственное требование для этого (кроме dlopen(), libdlи dlfcn.h) - знание аргументов и типа функции.

СС Энн
источник