Как добавить отражение в приложение С++?

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

Ответ 1

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

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

Ответ 2

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

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

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

Ответ 3

Есть два вида reflection плавающих вокруг.

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

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

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

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

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

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

Ответ 4

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

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

Ответ 5

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

Это просто неправильно. Фактически, сам термин RTTI был придуман стандартом С++. С другой стороны, RTTI не очень далеко продвигает отражение.

Ответ 6

Информация существует, но не в том формате, который вам нужен, и только если вы экспортируете свои классы. Это работает в 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-безопасность и т.д., Поэтому эти аспекты реализованы как проверки выполнения.

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

Ответ 7

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

Ответ 8

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

Ответ 9

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

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

Ответ 10

EDIT: CAMP больше не поддерживается; доступны две вилки:

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

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

В текущей библиотеке Tegesoft используется Boost, но есть также fork с использованием С++ 11, который больше не требует Boost.

Ответ 11

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

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

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

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

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

Ответ 12

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

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

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

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

Ответ 13

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

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

Ответ 14

Два отражающих решения, о которых я знаю из моих дней С++:

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

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

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

Удачи.

Ответ 15

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

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

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

CERN Reflex. Кажется, он работает очень хорошо.

Ответ 16

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

Ответ 17

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

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

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

Ответ 18

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

Итак, С++ не дает отражения, и нелегко "имитировать" его самостоятельно как общее правило, как отмечали другие ответы.

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

Наш DMS Software Reengineering Toolkit - это обобщенная технология компилятора, параметризованная явными определениями langauge. Он имеет определения langauge для C, С++, Java, COBOL, PHP,...

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

Ответ 19

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

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

Ответ 20

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

Ответ 21

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

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

Ответ 22

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

Поиск некоторых макросов или создание собственных. Qt также может помочь с отражением (если его можно использовать).

Ответ 23

Попробуйте посмотреть этот проект http://www.garret.ru/cppreflection/docs/reflect.html добавлены отражения на С++. Он добавил метаданные к классам, которые вы затем можете использовать.

Ответ 24

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

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

нижняя строка - отражение может окупиться, если сделано правильно, и это вполне возможно в С++.

Ответ 25

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

Ответ 26

Если вы ищете относительно простое 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 );

}

REFLECTABLE define использует имя класса + имя поля с offsetof -, чтобы определить, в каком месте в памяти находится конкретное поле. Я пытался подобрать терминологию .NET настолько, насколько это возможно, но 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

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

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

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.

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

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

Ответ 27

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

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

UPDATE: Существует очень простая реализация макроса 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 строк кода;)

Ответ 28

В С++ 20 вы получили операторы расширения, которые позволяют выполнять итерации по типам агрегатов:

struct my_type {
    double data;
    std::string another_data;
    int last_data;
};

auto object = my_type{};

for...(auto& member : object) {
    using member_type = std::remove_cvref_t<decltype(member)>;
    member = get_data<member_type>();
}

Ответ 29

Обновлено 24.2.2017

Раньше я анализировал поддержку использования #define и, как он рекомендовал в некоторых веб-статьях - я попал в разные определения на Visual С++, не работал идентично по сравнению с определением, используемым в gcc (например, в Интернете это довольно часто называют "прогулкой по MSVC" ). Кроме того, вы не сможете легко понять, что происходит за механизмом определения/макрорасширения - довольно сложно отлаживать каждое макрорасширение.

Есть несколько способов обойти сложности определения расширения, одним из подходов является включение флага компилятора "/P" (только предварительный процесс для файла), после чего вы можете сравнить, как открывается ваш определение. (Раньше я также активно использовал stringfy operator (#))

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

https://sourceforge.net/p/testcppreflect/code/HEAD/tree/MacroHelpers.h

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

Я вспомнил рабочий пример кода и поставил его как проект sourceforge, можно скачать здесь:

https://sourceforge.net/p/testcppreflect/code/HEAD/tree/

Демо-код выглядит следующим образом:

#include "CppReflect.h"
using namespace std;


class Person
{
public:
    REFLECTABLE( Person,
        (CString)   name,
        (int)       age
    )

};

class People
{
public:
    REFLECTABLE( People,
        (CString) groupName,
        (vector<Person>)  people
    )
};


void main(void)
{
    People ppl;
    ppl.groupName = "Group1";

    Person p;
    p.name = L"Roger";
    p.age = 37;
    ppl.people.push_back(p);
    p.name = L"Alice";
    p.age = 27;
    ppl.people.push_back( p );
    p.name = L"Cindy";
    p.age = 17;
    ppl.people.push_back( p );

    CStringA xml = ToXML( &ppl );
    CStringW errors;

    People ppl2;

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

}

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

Я использовал парсер pugi xml для извлечения демонстрационного кода в 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>

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

https://sourceforge.net/p/testcppreflect/code/HEAD/tree/TypeTraits.h#l65

template <>
class TypeTraitsT<CString> : public TypeTraits
{
public:
    virtual CStringW ToString( void* pField )
    { 
        CString* s = (CString*)pField;
        return *s;
    }

    virtual void FromString( void* pField, const wchar_t* value )
    { 
        CString* s = (CString*)pField;
        *s = value;
    }
};

template <>
class TypeTraitsT<int> : public TypeTraits
{
public:
    virtual CStringW ToString( void* pField )
    {
        int* p = (int*) pField;
        return std::to_string(*p).c_str();
    }

    virtual void FromString( void* pField, const wchar_t* value )
    {
        int* p = (int*)pField;
        *p = _wtoi(value);
    }
};

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