Как добавить отражение в приложение C++?
Я хотел бы иметь возможность проанализировать класс C++ на предмет его имени, содержимого (т.е. членов и их типов) и т. Д. Я говорю здесь на родном C++, а не на управляемом C++, в котором есть отражение. Я понимаю, что C++ предоставляет некоторую ограниченную информацию, используя RTTI. Какие дополнительные библиотеки (или другие методы) могут предоставить эту информацию?
30 ответов
Ponder - это библиотека отражений C++, в ответ на этот вопрос. Я рассмотрел варианты и решил сделать свои собственные, так как я не мог найти тот, который отмечал бы все мои коробки.
Хотя на этот вопрос есть отличные ответы, я не хочу использовать тонны макросов или полагаться на Boost. Boost - отличная библиотека, но есть множество небольших проектов C++0x, которые проще и быстрее компилируются. Существуют также преимущества возможности внешнего декорирования класса, например, обертывание библиотеки C++, которая (пока?) Не поддерживает C++11. Это форк CAMP, использующий C++11, который больше не требует Boost.
Вам нужно, чтобы препроцессор генерировал данные отражения о полях. Эти данные могут храниться как вложенные классы.
Во-первых, чтобы было проще и понятнее записать его в препроцессоре, мы будем использовать типизированное выражение. Типизированное выражение - это просто выражение, которое помещает тип в круглые скобки. Так что вместо того, чтобы писать 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 строках кода.
Есть два вида reflection
плавать вокруг.
- Проверка путем перебора членов типа, перечисления его методов и так далее.
Это невозможно с C++. - Проверка путем проверки того, имеет ли тип класса (class, struct, union) метод или вложенный тип, является производной от другого конкретного типа.
Такое возможно с помощью C++template-tricks
, использованиеboost::type_traits
для многих вещей (например, проверка, является ли тип целым). Для проверки существования функции-члена используйте Можно ли написать шаблон для проверки существования функции?, Для проверки существования определенного вложенного типа используйте обычный SFINAE.
Если вы скорее ищете способы выполнения 1), например, посмотрите, сколько методов у класса, или хотите получить строковое представление идентификатора класса, то я боюсь, что нет стандартного C++ способа сделать это. Вы должны использовать либо
- Мета-компилятор, такой как Qt Meta Object Compiler, который переводит ваш код, добавляя дополнительную мета-информацию.
- Framework, состоящий из макросов, которые позволяют вам добавлять необходимые метаинформации. Вам нужно будет сообщить структуре все методы, имена классов, базовые классы и все, что нужно.
C++ сделан с учетом скорости. Если вы хотите инспекции высокого уровня, как в C# или Java, то, боюсь, я должен сказать вам, что без некоторых усилий нет пути.
Отражение не поддерживается C++ из коробки. Это печально, потому что это делает оборонительные испытания болью.
Есть несколько подходов к рефлексии:
- использовать отладочную информацию (не переносимо).
- Обсыпайте ваш код макросами / шаблонами или другим исходным подходом (выглядит некрасиво)
- Измените компилятор, такой как clang / gcc, чтобы создать базу данных.
- Используйте подход Qt moc
- Boost Reflect
- Точное и плоское отражение
Первая ссылка выглядит наиболее многообещающе (использует мод для лязга), вторая обсуждает ряд методов, третья - это другой подход с использованием gcc:
В настоящее время существует рабочая группа для отражения C++. Смотрите новости для C++ 14 @ CERN:
Изменить 13/08/17: С момента первоначального поста было несколько потенциальных улучшений в отражении. Ниже приводится более подробная информация и обсуждение различных методов и статуса:
Однако он не выглядит многообещающим в отношении стандартизированного подхода к размышлениям в C++ в ближайшем будущем, если сообщество не проявит гораздо большего интереса к поддержке отражения в C++.
Ниже приведена подробная информация о текущем состоянии на основе отзывов с последнего совещания по стандартам C++:
Изменить 13/12/2017
Отражение, похоже, движется к C++ 20 или, более вероятно, к TSR. Движение однако медленное.
- Зеркало
- Зеркало стандартное предложение
- Зеркальная бумага
- Херб Саттер - метапрограммирование, включая рефлексию
Изменить 15/09/2018
Проект ТС был разослан в национальные органы для голосования.
Текст можно найти здесь: https://github.com/cplusplus/reflection-ts
И я бы хотел пони, но пони не бесплатны.:-п
http://en.wikibooks.org/wiki/C%2B%2B_Programming/RTTI - это то, что вы собираетесь получить. Отражение, о котором вы думаете, - полностью описательные метаданные, доступные во время выполнения, - просто не существует для C++ по умолчанию.
Информация существует - но не в том формате, который вам нужен, и только в том случае, если вы экспортируете свои классы. Это работает в 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 и т. Д., И нет необходимости выводить из определенного базового класса. Если есть достаточно спроса, возможно, мы могли бы привести его в форму для выпуска.
RTTI не существует для C++.
Это просто неправильно. На самом деле, сам термин "RTTI" был придуман стандартом C++. С другой стороны, RTTI не идет слишком далеко в реализации рефлексии.
Я бы порекомендовал использовать Qt.
Существует лицензия с открытым исходным кодом, а также коммерческая лицензия.
Вам нужно посмотреть, что вы пытаетесь сделать, и будет ли 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
это преобразуется в серию вызовов методов во время выполнения (когда читается файл конфигурации), так что это довольно эффективно. Я бы не советовал делать это, если вам действительно не нужно, но когда вы это делаете, вы можете делать действительно классные вещи.
Что вы пытаетесь сделать с отражением?
Вы можете использовать черты типа Boost и библиотеки typeof как ограниченную форму отражения во время компиляции. То есть вы можете проверять и изменять основные свойства типа, передаваемого в шаблон.
РЕДАКТИРОВАТЬ: CAMP больше не поддерживается; доступны две вилки:
- Один также называется CAMP и основан на том же API.
- Ponder - это частичное переписывание, и оно должно быть предпочтительным, так как не требует Boost; он использует C++11.
CAMP - это лицензионная библиотека MIT (ранее LGPL), которая добавляет отражение в язык C++. Он не требует определенного этапа предварительной обработки при компиляции, но привязка должна быть сделана вручную.
Текущая библиотека Tegesoft использует Boost, но есть также форк, использующий C++11, который больше не требует Boost.
Однажды я сделал что-то вроде того, что вам нужно, и хотя возможно получить некоторый уровень отражения и доступа к высокоуровневым функциям, головная боль от обслуживания может не стоить того. Моя система использовалась для полного отделения классов пользовательского интерфейса от бизнес-логики посредством делегирования, схожего с концепцией Objective-C передачи и пересылки сообщений. Способ сделать это - создать некоторый базовый класс, способный отображать символы (я использовал пул строк, но вы могли бы сделать это с помощью перечислений, если вы предпочитаете обработку ошибок во время компиляции, а не полную гибкость) функциям-указателям (на самом деле это не так). чистые указатели на функции, но что-то похожее на то, что есть в Boost с Boost.Function - к которому у меня не было доступа в то время). Вы можете сделать то же самое для переменных-членов, если у вас есть некоторый общий базовый класс, способный представлять любое значение. Вся система была безошибочным грабежом кодирования и делегирования ключей с несколькими побочными эффектами, которые, возможно, стоили огромного количества времени, необходимого для того, чтобы каждый класс, использующий систему, сопоставил все свои методы и элементы с законными вызовами.: 1) Любой класс может вызывать любой метод любого другого класса без необходимости включать заголовки или писать поддельные базовые классы, чтобы интерфейс мог быть предопределен для компилятора; и 2) геттеры и сеттеры переменных-членов было легко сделать потокобезопасными, потому что изменение или доступ к их значениям всегда делался с помощью 2 методов в базовом классе всех объектов.
Это также привело к возможности делать действительно странные вещи, которые в C++ были бы непростыми. Например, я мог бы создать объект Array, который содержал произвольные элементы любого типа, включая самого себя, и динамически создавать новые массивы, передавая сообщение всем элементам массива и собирая возвращаемые значения (аналогично map в Lisp). Другой была реализация наблюдения значения ключа, благодаря которой я смог настроить пользовательский интерфейс для немедленного реагирования на изменения в членах внутренних классов вместо того, чтобы постоянно опрашивать данные или излишне перерисовывать отображение.
Возможно, более интересным для вас является тот факт, что вы также можете сбросить все методы и члены, определенные для класса, и в виде строки не меньше.
Недостатки системы, которые могут отговорить вас от беспокойства: добавление всех сообщений и значений ключей чрезвычайно утомительно; это медленнее, чем без каких-либо размышлений; ты будешь ненавидеть видеть boost::static_pointer_cast
а также boost::dynamic_pointer_cast
по всей вашей кодовой базе с неистовой страстью; ограничения строго типизированной системы все еще существуют, вы просто их немного скрываете, так что это не так очевидно. Опечатки в ваших строках также не являются забавным или легко обнаруживаемым сюрпризом.
Что касается того, как реализовать что-то вроде этого: просто используйте общие и слабые указатели на какую-то общую базу (мой был очень образно назван "Объект") и извлекайте для всех типов, которые вы хотите использовать. Я бы порекомендовал установить Boost.Function вместо того, чтобы делать это так, как я делал, что было с какой-то нестандартной ерундой и кучей уродливых макросов для переноса вызовов указателя функции. Поскольку все сопоставлено, проверка объектов - это просто итерация по всем ключам. Поскольку мои классы были практически максимально приближены к прямому грабежу Какао с использованием только C++, если вы хотите что-то подобное, я бы предложил использовать документацию по Какао в качестве образца.
Два похожих на рефлексии решения, которые я знаю по моим дням в C++:
1) Используйте RTTI, который обеспечит вам начальную загрузку для построения вашего поведения, похожего на отражение, если вы сможете получить все ваши классы из базового класса 'object'. Этот класс может предоставлять некоторые методы, такие как GetMethod, GetBaseClass и т. Д. Что касается работы этих методов, вам нужно будет вручную добавить несколько макросов для украшения ваших типов, которые за кулисами создают метаданные в типе для предоставления ответов на GetMethods и т. Д.
2) Другой вариант, если у вас есть доступ к объектам компилятора, это использовать DIA SDK. Если я правильно помню, это позволяет вам открывать pdbs, которая должна содержать метаданные для ваших типов C++. Этого может быть достаточно, чтобы сделать то, что вам нужно. На этой странице показано, как вы можете получить все базовые типы класса, например.
Оба эти решения немного уродливы, хотя! Нет ничего лучше, чем немного C++, чтобы вы могли оценить роскошь C#.
Удачи.
РЕДАКТИРОВАТЬ: Обновленная неработающая ссылка по состоянию на 7 февраля 2017 года.
Я думаю, что никто не упомянул это:
В CERN они используют систему полного отражения для C++:
ЦЕРН Рефлекс. Кажется, работает очень хорошо.
Этот вопрос сейчас немного стар (не знаю, почему я продолжаю задавать старые вопросы сегодня), но я думал о BOOST_FUSION_ADAPT_STRUCT, который вводит отражение во время компиляции.
Конечно, вы должны отобразить это на отражение во время выполнения, и это не будет слишком легко, но это возможно в этом направлении, хотя это не будет наоборот:)
Я действительно думаю, что макрос для инкапсуляции BOOST_FUSION_ADAPT_STRUCT
Можно создать необходимые методы, чтобы получить поведение во время выполнения.
Я думаю, что вы могли бы найти интересную статью Доминика Филиона "Использование шаблонов для отражения в C++". Он находится в разделе 1.4 Gems 5 для программирования игр. К сожалению, у меня нет своей копии со мной, но ищите ее, потому что я думаю, что это объясняет то, что вы просите.
Библиотека RareCpp обеспечивает довольно простое и интуитивно понятное отражение - вся информация о полях / типах предназначена либо для доступа в массивах, либо для доступа к ним. Он написан для 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, 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 ]
}
Смотрите также...
Отражение - это, по сути, то, что компилятор решил оставить в виде следов в коде, который может запросить код времени выполнения. C++ славится тем, что не платит за то, что вы не используете; Поскольку большинство людей не используют / не хотят отражения, компилятор C++ избегает затрат, ничего не записывая.
Таким образом, C++ не дает отражения, и его не так просто "смоделировать" как общее правило, как отмечали другие ответы.
В разделе "другие методы", если у вас нет языка с отражением, найдите инструмент, который может извлекать нужную вам информацию во время компиляции.
Наш инструментарий реинжиниринга программного обеспечения DMS представляет собой обобщенную технологию компиляции, параметризованную явными определениями языка. У этого есть определения языка для C, C++, Java, COBOL, PHP, ...
Для версий C, C++, Java и COBOL он обеспечивает полный доступ к деревьям разбора и информации таблицы символов. Эта информация таблицы символов включает в себя данные, которые вы, вероятно, захотите получить от "отражения". Если ваша цель состоит в том, чтобы перечислить некоторый набор полей или методов и что- то с ними сделать, DMS можно использовать для преобразования кода в соответствии с тем, что вы найдете в таблицах символов произвольным образом.
Проверьте Classdesc http://classdesc.sf.net/. Он обеспечивает отражение в форме "дескрипторов" класса, работает с любым стандартным компилятором C++ (да, известно, что он работает как с Visual Studio, так и с GCC) и не требует аннотации исходного кода (хотя существуют некоторые прагмы для обработки сложных ситуаций.). Он разрабатывался более десяти лет и использовался в ряде проектов промышленного масштаба.
Вы можете найти другую библиотеку здесь: http://www.garret.ru/cppreflection/docs/reflect.html Она поддерживает 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/blob/master/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
определение использует имя класса + имя поля с 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
,
Но поскольку тема действительно продвинутая - рекомендую сначала проверить видео.
Если у вас есть идеи по улучшению, не стесняйтесь связаться со мной.
Похоже, C++ до сих пор не имеет этой функции. И C++ 11 тоже отложил отражение ((
Ищите некоторые макросы или делайте собственные. Qt также может помочь с отражением (если это можно использовать).
Я хотел бы рекламировать существование автоматического инструментария самоанализа / отражения "ИДК". Он использует мета-компилятор, такой как Qt, и добавляет метаинформацию непосредственно в объектные файлы. Утверждается, что он прост в использовании. Нет внешних зависимостей. Он даже позволяет автоматически отражать std::string и затем использовать его в скриптах. Пожалуйста, посмотрите на ИДК
Попробуйте посмотреть на этот проект http://www.garret.ru/cppreflection/docs/reflect.html- добавлены размышления в C++. Он добавил метаданные в классы, которые вы можете затем использовать.
Когда я хотел отражения в C++, я прочитал эту статью и улучшил то, что увидел там. Извините, не могу. Я не владею результатом... но вы, конечно, можете получить то, что у меня было, и пойти оттуда.
В настоящее время я исследую, когда мне так хочется, методы, которые можно использовать для использования attribute_linearly, чтобы упростить определение отражаемых типов. Я довольно далеко продвинулся в этом деле, но у меня все еще есть пути. Изменения в C++0x, скорее всего, окажут большую помощь в этой области.
Даже если в C++ отражение не поддерживается "из коробки", его не сложно реализовать. Я встречал эту замечательную статью: http://replicaisland.blogspot.co.il/2010/11/building-reflective-object-system-in-c.html
В статье подробно объясняется, как можно реализовать довольно простую и элементарную систему отражения. предоставил его не самое полезное решение, и есть еще острые края, которые нужно разобрать, но для моих нужд этого было достаточно.
нижняя строка - отражение может окупиться, если все сделано правильно, и это вполне осуществимо в C++.
Отражение в 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 строк кода;)
Вы можете получить классные функции статического отражения для структур с помощью BOOST_HANA_DEFINE_STRUCT из библиотеки Boost::Hana.
Hana довольно универсален не только для того случая, который вы имеете в виду, но и для множества метапрограммирований шаблонов.
В C++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>();
}