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





И я бы хотел пони, но пони не бесплатны. :-п
http://en.wikibooks.org/wiki/C%2B%2B_Programming/RTTI - вот что вы получите. Отражение, о котором вы думаете - полностью описательные метаданные, доступные во время выполнения - просто не существует для C++ по умолчанию.
Я второй бред. Шаблоны C++ могут быть довольно мощными, и существует богатый опыт работы с различными типами поведения «отражения», такими как ускорение «любой» библиотеки, свойств типов, C++ RTTI и т. д., Которые могут решить многие проблемы, для которых решено отражение. Итак, Ник, какова твоя цель здесь?
Проголосуйте за замечание пони! Я бы проголосовал дважды, так как ваш ответ тоже заслуживает этого, но, к сожалению, я получил только один, так что пони побеждают. :-)
Я действительно не понимаю, почему это умный ответ. Я уже сказал, что мне нужны ссылки на библиотеки и т. д., Чтобы реализовать это. Отражение / самоанализ предназначен для различных систем, чтобы разрешить доступ к сценариям, сериализацию и т. д.
@Nick: Он уже ответил на это. Это невозможно, данные не существуют, и поэтому ни одна библиотека не может реализовать это за вас.
@jalf Все еще странно для меня читать, как люди в мире программирования говорят, что они думают как «это невозможно», а не «я не знаю как». Конечно, метаданные не существуют, но их можно вставить с помощью макросов.
Что вы пытаетесь делать с отражением?
Вы можете использовать библиотеки Boost типовые черты и тип как ограниченную форму отражения во время компиляции. То есть вы можете проверять и изменять основные свойства типа, переданного в шаблон.
Два рефлексивных решения, о которых я знаю по работе на C++, следующие:
1) Используйте RTTI, который предоставит вам начальную загрузку для построения вашего поведения, подобного отражению, если вы можете заставить все свои классы унаследовать от базового класса «объект». Этот класс может предоставлять некоторые методы, такие как GetMethod, GetBaseClass и т. д. Что касается того, как эти методы работают, вам нужно будет вручную добавить некоторые макросы для украшения ваших типов, которые за кулисами создают метаданные в типе для предоставления ответов на GetMethods и т. д.
2) Другой вариант, если у вас есть доступ к объектам компилятора, - использовать DIA SDK. Если я правильно помню, это позволяет вам открывать pdbs, которые должны содержать метаданные для ваших типов C++. Возможно, этого будет достаточно, чтобы сделать то, что вам нужно. Эта страница показывает, например, как можно получить все базовые типы класса.
Хотя оба эти решения немного уродливы! Нет ничего лучше, чем немного C++, чтобы вы оценили роскошь C#.
Удачи.
Это хитроумный и гигантский прием с предложенным вами там DIA SDK.
Я бы рекомендовал использовать Qt.
Существует как лицензия с открытым исходным кодом, так и коммерческая лицензия.
Я смотрел на это, но он использует макросы, а исходный код требует синтаксического анализа для генерации кода метаданных. Я бы хотел избежать этого лишнего шага. Я бы предпочел использовать библиотеку C++ или простые макросы. Спасибо за идею.
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 это преобразуется в серию вызовов методов во время выполнения (когда читается файл конфигурации), поэтому он довольно эффективен. Я бы не советовал делать это, если в этом нет необходимости, но когда вы это сделаете, вы сможете делать действительно классные вещи.
должен любить эти функции, которые всегда возвращают истину;) Я полагаю, это невосприимчиво к проблемам статического упорядочивания инициализации?
Думаю, вам может быть интересна статья Доминика Филиона «Использование шаблонов для отражения в C++». Он находится в разделе 1.4 Самоцветы игрового программирования 5. К сожалению, у меня нет с собой своей копии, но ищите ее, потому что я думаю, что она объясняет то, о чем вы просите.
Есть два вида плавания reflection.
template-tricks. Используйте boost::type_traits для многих вещей (например, для проверки целостности типа). Для проверки существования функции-члена используйте Можно ли написать шаблон для проверки существования функции?. Чтобы проверить, существует ли определенный вложенный тип, используйте простой СФИНАЕ.Если вы скорее ищете способы выполнить 1), например, посмотреть, сколько методов имеет класс или получить строковое представление идентификатора класса, я боюсь, что стандартного способа C++ для этого не существует. Вы должны использовать либо
C++ создан с учетом скорости. Если вам нужен высокоуровневый анализ, такой как в C# или Java, то, боюсь, я должен вам сказать, что без некоторых усилий это невозможно.
C++ создан с учетом скорости, но философия заключается не в том, чтобы «максимально быстро», а в том, что «вы не платите за это, если не используете его». Я считаю, что язык может реализовать интроспекцию таким образом, чтобы это соответствовало этой философии, в C++ этого просто нет.
@ Джозеф: Как это должно быть сделано? Для этого потребуется сохранить все эти метаданные. Это означает, что вы должны заплатить за это, даже если вы им не пользуетесь. (Если вы не можете пометить отдельные типы как «поддерживающие отражение», но тогда мы почти подошли к концу, где мы могли бы с таким же успехом использовать существующие макросы.
@jalf: только те метаданные, которые могут понадобиться. Если мы рассматриваем только отражение во время компиляции, это тривиально. Например. функция времени компиляции members<T>, которая возвращает список всех членов T. Если бы мы хотели иметь отражение во время выполнения (т.е. RTTI, смешанное с отражением), компилятор все равно знал бы все отраженные базовые типы. Вполне вероятно, что members<T>(T&) никогда не будет создан для T = std :: string, поэтому нет необходимости включать RTTI для std :: string или его производных классов.
Библиотека reflex (упомянутая ниже) добавляет отражение в C++ без замедления существующего кода по адресу: root.cern.ch/drupal/content/reflex
@Joe: Reflection никогда не замедляет существующий код. Это просто увеличивает размер доставляемого материала (поскольку вам нужно предоставить базу данных с информацией о типах ...).
У всего есть цена. Даже если у вас нет доступа к метаданным, вы платите за них память во время выполнения. В некоторых мирах нет такой роскоши.
Итерация по членам в C++ возможна. Это просто неудобно и обычно не стоит усилий. Рассмотрим, когда таблица построена с данными type_traits и образцами объектов вместе в разнородном контейнере, чтобы включить поиск, используемый для отмены стирания типа. Это ОЧЕНЬ шаблонный, и большинство вещей, которые можно сделать с его помощью, лучше выполнить другими способами.
@Joe, ссылка, которую вы даете, похоже, переехала. Этот вариант все еще активен: seal-reflex.web.cern.ch/seal-reflex/examples.html
Я сделал что-то вроде того, что вам нужно однажды, и хотя можно получить некоторый уровень размышлений и доступ к функциям более высокого уровня, головная боль обслуживания может не стоить того. Моя система использовалась для того, чтобы классы пользовательского интерфейса были полностью отделены от бизнес-логики посредством делегирования, схожего с концепцией передачи и пересылки сообщений в Objective-C. Способ сделать это - создать некоторый базовый класс, способный отображать символы (я использовал пул строк, но вы могли бы сделать это с помощью перечислений, если вы предпочитаете скорость и обработку ошибок во время компиляции, а не полную гибкость) для указателей функций (на самом деле не чистые указатели на функции, но что-то похожее на то, что Boost имеет с Boost.Function - к которым у меня не было доступа в то время). Вы можете сделать то же самое для своих переменных-членов, если у вас есть общий базовый класс, способный представлять любое значение. Вся система была беззастенчивой копией кодирования и делегирования ключевого значения с несколькими побочными эффектами, которые, возможно, стоили огромного количества времени, необходимого для того, чтобы каждый класс, который использовал систему, сопоставил все его методы и члены с законными вызовами. : 1) Любой класс может вызывать любой метод любого другого класса без необходимости включать заголовки или писать поддельные базовые классы, чтобы интерфейс мог быть предопределен для компилятора; и 2) геттеры и сеттеры переменных-членов было легко сделать потокобезопасным, потому что изменение или доступ к их значениям всегда выполнялись с помощью 2 методов в базовом классе всех объектов.
Это также привело к возможности делать некоторые действительно странные вещи, которые в противном случае были бы непростыми в C++. Например, я мог бы создать объект Array, содержащий произвольные элементы любого типа, включая самого себя, и динамически создавать новые массивы, передавая сообщение всем элементам массива и собирая возвращаемые значения (аналогично map в Lisp). Другой была реализация наблюдения за ключом и значением, в результате чего я смог настроить пользовательский интерфейс для немедленного реагирования на изменения в членах внутренних классов вместо постоянного опроса данных или ненужного перерисовки отображения.
Возможно, более интересным для вас является тот факт, что вы также можете выгрузить все методы и члены, определенные для класса, и не меньше в строковой форме.
Обратные стороны системы, которые могут отпугнуть вас от беспокойства: добавление всех сообщений и пар "ключ-значение" чрезвычайно утомительно; это медленнее, чем без отражения; вы с неистовой страстью возненавидите boost::static_pointer_cast и boost::dynamic_pointer_cast по всей кодовой базе; ограничения строго типизированной системы все еще существуют, вы на самом деле просто их немного скрываете, чтобы это не было так очевидно. Опечатки в ваших строках тоже не доставляют удовольствия и не вызывают удивления.
Что касается того, как реализовать что-то вроде этого: просто используйте общие и слабые указатели на некую общую базу (мой очень образно назван «Object») и производите от всех типов, которые вы хотите использовать. Я бы порекомендовал установить Boost.Function вместо того, чтобы делать это так, как я, то есть с некоторой кастомной чушью и множеством уродливых макросов для обертывания вызовов указателя функций. Поскольку все отображается, проверка объектов - это просто итерация по всем ключам. Поскольку мои классы были, по сути, максимально приближены к прямому копированию какао с использованием только C++, если вам нужно что-то подобное, я бы предложил использовать документацию какао в качестве образца.
Привет, @Michael; у вас еще есть исходный код для этого, или вы избавились от него? Я хотел бы взглянуть на это, если вы не против.
Упс, ваше имя написано неправильно! Нет задаваться вопросом Мне так и не ответили…
Информация существует, но не в том формате, который вам нужен, и только если вы экспортируете свои классы. Это работает в 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-security и т. д., Поэтому эти аспекты реализованы как проверки во время выполнения.
Я уверен, что синтаксис можно улучшить, и пока он работает только на Win32 и Win64. Мы обнаружили, что это действительно полезно для создания автоматических интерфейсов GUI для классов, создания свойств на C++, потоковой передачи в и из XML и т. д., И нет необходимости наследовать от определенного базового класса. Если будет достаточно спроса, возможно, мы сможем придать ему форму для выпуска.
Я думаю, вы имеете в виду __declspec(dllexport), и вы можете получить информацию из файла .map, если вы разрешите создание такого во время сборки.
Когда мне понадобилось отражение в C++, я прочитал Эта статья и улучшил то, что я там увидел. Извините, банку нет. Я не владею результатом ... но вы определенно можете получить то, что было у меня, и продолжить работу.
В настоящее время я исследую, когда мне захочется, методы использования inherit_linearly, чтобы значительно упростить определение отражаемых типов. На самом деле я довольно далеко продвинулся в этом, но у меня еще есть пути. Изменения в C++ 0x, скорее всего, очень помогут в этой области.
РЕДАКТИРОВАТЬ: ЛАГЕРЬ больше не поддерживается; доступны две вилки:
ЛАГЕРЬ - это лицензированная библиотека MIT (ранее LGPL), которая добавляет отражение в язык C++. Это не требует определенного шага предварительной обработки при компиляции, но привязка должна выполняться вручную.
Текущая библиотека Tegesoft использует Boost, но есть также вилка, использующий C++ 11, который больше не требует Boost.
Этот вопрос немного устарел (не знаю, почему я продолжаю отвечать на старые вопросы сегодня), но я думал о BOOST_FUSION_ADAPT_STRUCT, который вводит отражение во время компиляции.
Конечно, вы должны сопоставить это с отражением во время выполнения, и это будет не так просто, но это возможно в этом направлении, а не в обратном :)
Я действительно думаю, что макрос для инкапсуляции BOOST_FUSION_ADAPT_STRUCT может сгенерировать необходимые методы для получения поведения во время выполнения.
от minghua (который изначально редактировал сообщение): Я углубился в это решение BOOST_FUSION_ADAPT_STRUCT и в конце концов придумал пример. См. Этот новый вопрос SO - C++ итерация во вложенном поле структуры с помощью boost fusion adap_struct.
Отлично, Матье! Просто понял, что видел ваши намеки здесь и там в течение прошлого года. Не заметил, что они связаны до сих пор. Это было очень вдохновляюще.
Отражение - это, по сути, то, что компилятор решил оставить в качестве следов в коде, которые может запрашивать код среды выполнения. C++ известен тем, что не платит за то, что не используете; поскольку большинство людей не используют / не хотят отражения, компилятор C++ избегает затрат, не записывая что-либо.
Итак, C++ не обеспечивает отражения, и его нелегко «смоделировать» самостоятельно в качестве общего правила, как отмечали другие ответы.
В разделе «Другие методы», если у вас нет языка с отражением, получите инструмент, который может извлекать нужную информацию во время компиляции.
Наша Набор инструментов для реинжиниринга программного обеспечения DMS - это обобщенная технология компилятора, параметризованная явными определениями языка. В нем есть определения языков для C, C++, Java, COBOL, PHP, ...
Для версий C, C++, Java и COBOL он обеспечивает полный доступ к деревьям синтаксического анализа и информации таблицы символов. Эта информация таблицы символов включает в себя данные, которые вы, вероятно, захотите получить от «отражения». Если вы хотите перечислить некоторый набор полей или методов и что-то с ними делать, DMS можно использовать для преобразования кода в соответствии с тем, что вы найдете в таблицах символов, произвольными способами.
Вы можете найти другую библиотеку здесь: http://www.garret.ru/cppreflection/docs/reflect.html Он поддерживает 2 способа: получение информации о типе из отладочной информации и предоставление этой информации программисту.
Я также заинтересовался отражением моего проекта и нашел эту библиотеку, я еще не пробовал, но пробовал другие инструменты от этого парня, и мне нравится, как они работают :-)
Похоже, что в C++ до сих пор нет этой функции. И C++ 11 тоже отложил рефлексию ((
Найдите несколько макросов или создайте собственные. Qt также может помочь с отражением (если его можно использовать).
Что вам нужно сделать, так это заставить препроцессор сгенерировать данные отражения о полях. Эти данные могут храниться как вложенные классы.
Во-первых, чтобы упростить и упростить запись в препроцессоре, мы будем использовать типизированное выражение. Типизированное выражение - это просто выражение, которое помещает тип в круглые скобки. Поэтому вместо 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 строк кода.
Престижность за то, что вы показали, как реализовать отражение, а не за то, что это невозможно. Именно такие ответы заставляют С.О. отличный ресурс.
Обратите внимание, что если вы попытаетесь скомпилировать это в 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,)
Я получаю сообщение об ошибке «BOOST_PP_IIF_0» не называет тип. Не могли бы вы помочь.
См. Мой собственный ответ - stackoverflow.com/a/28399807/2338477 Я извлек и перепаковал все определения, и библиотека boost не нужна. В качестве демонстрационного кода я предоставляю сериализацию в xml и восстановление из xml.
Отражение не поддерживается C++ из коробки. Это печально, потому что из-за этого защитное тестирование становится болезненным.
Есть несколько подходов к рефлексии:
Первая ссылка выглядит наиболее многообещающей (использует мод для clang), вторая обсуждает ряд методов, третья - другой подход с использованием gcc:
Сейчас существует рабочая группа по отражению в C++. Смотрите новости о C++ 14 @ CERN:
Изменить 13/08/17:
Со времени первоначального поста в отражении произошел ряд потенциальных улучшений. Ниже приводится более подробная информация и обсуждение различных методов и статуса:
Однако в ближайшем будущем это не выглядит многообещающим с точки зрения стандартизованного подхода к отражению в C++, если только сообщество не проявит гораздо большего интереса к поддержке отражения в C++.
Ниже приводится подробная информация о текущем статусе, основанная на обратной связи с последним соответствием стандартов C++:
Изменить 13/12/2017
Похоже, что Reflection движется к C++ 20 или, что более вероятно, к TSR. Однако движение медленное.
Изменить 15.09.2018
Проект ТЗ направлен в национальные органы для голосования.
Текст можно найти здесь: https://github.com/cplusplus/reflection-ts
Изменить 11.07.2019
Техническое описание отражения полнофункционально, и оно будет отправлено для комментариев и голосования летом (2019 г.).
Подход к программированию мета-шаблонов должен быть заменен более простым подходом кода времени компиляции (не отраженным в TS).
Редактировать 02.10.2020
Здесь есть запрос на поддержку TS отражения в Visual Studio:
Разговор о TS автора Дэвид Санкель:
Редактировать 17 марта 2020 г.
Достигнут прогресс в размышлениях. Отчет о поездке комитета ISO C++ в Праге за 2020-02 гг. Можно найти здесь:
Подробности о том, что рассматривается для C++ 23, можно найти здесь (включая небольшой раздел по отражению):
Редактировать 4 июня 2020 г.
Джефф Прешинг выпустил новый фреймворк под названием «Plywood», который содержит механизм отражения во время выполнения. Более подробную информацию можно найти здесь:
Инструменты и подход на сегодняшний день кажутся наиболее отточенными и простыми в использовании.
Редактировать 12 июля 2020 г.
Clang экспериментальная отражающая вилка: https://github.com/lock3/meta/wiki
Интересная библиотека отражения, которая использует библиотеку инструментов clang для извлечения информации для простого отражения без необходимости добавления макросов: https://github.com/chakaz/reflang
Изменить 24 февраля 2021 г.
Некоторые дополнительные подходы к инструментам clang:
Ссылка на cern не работает.
Ссылки на cern должны быть исправлены. Они имеют тенденцию довольно часто ломаться, что вызывает боль.
Этот ответ касается только отражения во время компиляции?
@einpoklum единственные текущие решения для отражения - это время компиляции, обычно с кодом мета-шаблона или макросами. Последний черновик TS выглядит так, как будто он должен работать во время выполнения, но вам нужно будет собрать все библиотеки с правильным компилятором для хранения необходимых метаданных.
@DamianDixon: Это неправда. Есть несколько библиотек отражения времени выполнения. Теперь, конечно, они довольно неуклюжие и либо соглашаются, либо требуют модификации компилятора, но они все еще существуют. Если, насколько я понимаю ваш комментарий, вы упомянули только отражение во время компиляции, отредактируйте свой ответ, чтобы он был более ясным.
@einpoklum, не могли бы вы предоставить ссылки на эти библиотеки отражения времени выполнения?
@DamianDixon: Вот один пример: Размышлять.
@einpoklum ponder по-прежнему требует от вас использования макросов, которые расширяются до шаблонов, а не во время выполнения. Я знаю, что вы потенциально можете использовать символы отладки как отладчик, но это зависит от платформы и компилятора.
Это невероятный ответ. Было бы неплохо иметь больше ссылок на библиотеки отражения, которые все еще обновляются.
Несмотря на то, что отражение не поддерживается в C++, его нетрудно реализовать. Я наткнулся на эту замечательную статью: http://replicaisland.blogspot.co.il/2010/11/building-reflective-object-system-in-c.html
в статье очень подробно объясняется, как можно реализовать довольно простую и элементарную систему отражения. предоставил это не самое полезное решение, и остались неровности, которые нужно было устранить, но для моих нужд этого было достаточно.
Итог - отражение может окупиться, если все сделано правильно, и это полностью осуществимо в C++.
Проверьте Classdesc http://classdesc.sf.net. Он обеспечивает отражение в форме «дескрипторов» класса, работает с любым стандартным компилятором C++ (да, он, как известно, работает с Visual Studio, а также с GCC) и не требует аннотации исходного кода (хотя существуют некоторые прагмы для обработки сложных ситуаций. ). Он разрабатывался более десяти лет и использовался в ряде проектов промышленного масштаба.
Добро пожаловать в Stack Overflow. Хотя этот ответ по теме, важно указать, что вы являетесь автором этого программного обеспечения, чтобы было ясно, что это не беспристрастная рекомендация :-)
Обновлено: обновлена неработающая ссылка по состоянию на 7 февраля 2017 г.
Думаю, об этом никто не упоминал:
В CERN используют систему полного отражения для C++:
ЦЕРН Рефлекс. Вроде работает очень хорошо.
@ j4nbur53 Ссылка не работает, потому что кажется, что они достигли определенного рубежа: root.cern.ch
Неужели вы имеете в виду эту ссылку root.cern.ch/root/doc/ROOTUsersGuideHTML/ch07.html Chapter Reflex?
Попробуйте этот root.cern.ch/how/how-use-reflex. Reflex работает как генератор, который анализирует ваши файлы заголовков и генерирует код / библиотеку интроспекции C++, с которыми вы можете связать и использовать простой api.
Я хотел бы прорекламировать существование инструментария автоматического самоанализа / рефлексии "IDK". Он использует мета-компилятор, такой как Qt, и добавляет метаинформацию непосредственно в объектные файлы. Утверждается, что им легко пользоваться. Никаких внешних зависимостей. Он даже позволяет автоматически отображать std :: string, а затем использовать его в скриптах. Пожалуйста, посмотрите я не знаю
Размышлять - это библиотека отражения C++, отвечающая на этот вопрос. Я рассмотрел варианты и решил сделать свой, так как я не смог найти ни одного, подходящего для всех моих полей.
Хотя на этот вопрос есть отличные ответы, я не хочу использовать тонны макросов или полагаться на Boost. Boost - отличная библиотека, но существует множество небольших проектов C++ 0x, сделанных на заказ, которые проще и имеют более быстрое время компиляции. Есть также преимущества в возможности внешнего оформления класса, например, обертывание библиотеки C++, которая (пока?) Не поддерживает C++ 11. Это форк CAMP, использующий C++ 11, больше не требует Boost.
Отражение в C++ очень полезно, в тех случаях, когда вам нужно запустить какой-то метод для каждого члена (например: сериализация, хеширование, сравнение). Я пришел с общим решением с очень простым синтаксисом:
struct S1
{
ENUMERATE_MEMBERS(str,i);
std::string str;
int i;
};
struct S2
{
ENUMERATE_MEMBERS(s1,i2);
S1 s1;
int i2;
};
Где ENUMERATE_MEMBERS - это макрос, который описан позже (ОБНОВЛЕНИЕ):
Предположим, мы определили функцию сериализации для 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 строк кода;)
Если вы объявите указатель на такую функцию:
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) - знать аргументы и тип функции.
Если вы ищете относительно простое отражение C++ - я собрал макро / определения из различных источников и прокомментировал их работу. Вы можете скачать заголовок файлы отсюда:
https://github.com/tapika/TestCppReflect/blob/master/MacroHelpers.h
набор определений, плюс дополнительные функции:
https://github.com/tapika/TestCppReflect/blob/master/CppReflect.hhttps://github.com/tapika/TestCppReflect/blob/master/CppReflect.cpphttps://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 define использует имя класса + имя поля с offsetof - чтобы определить, в каком месте памяти находится конкретное поле. Я попытался усвоить терминологию .NET, насколько это возможно, но C++ и C# отличаются, так что это не 1 к 1. Вся модель отражения C++ находится в классах 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>
Также возможно включить поддержку любых сторонних классов / структур через класс 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://thewikihow.com/video_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.
Но поскольку тема действительно продвинутая - рекомендую сначала просмотреть видео.
Если у вас есть идеи по улучшению, не стесняйтесь обращаться ко мне.
Вы можете получить классные функции статического отражения для структур с помощью BOOST_HANA_DEFINE_STRUCT из библиотеки Boost :: Hana. Hana довольно универсален не только для того случая использования, который вы имеете в виду, но и для множества метапрограммирований шаблонов.
Библиотека 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 ]
}
Смотрите также...
К сожалению, вы не можете сделать это без макросов и другой предварительной обработки, потому что требуются метаданные не существует, если вы не создадите их вручную с помощью некоторой магии предварительной обработки макросов.