Як я можу додати роздуми до програми C ++?


263

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


18
Сувора удача, ви не можете обійтися без макросів та інших попередніх обробок, тому що потрібні метадані не існують, якщо ви не створили їх вручну через магію попередньої обробки макросу.
джельф

6
Інформації, яку ви можете отримати від RTTI, недостатньо для того, щоб зробити більшість речей, про які ви насправді хочете задуматися. Наприклад, ви не можете перебирати функції учасника класу.
Джозеф Гарвін

Відповіді:


259

Що вам потрібно зробити, це запросити препроцесора генерувати відображаючі дані про поля. Ці дані можуть зберігатися як вкладені класи.

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

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

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

Далі ми визначаємо REFLECTABLEмакрос для генерування даних про кожне поле (плюс саме поле). Цей макрос буде називатися так:

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

Отже, використовуючи Boost.PP, ми повторюємо кожен аргумент та генеруємо такі дані:

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

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


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

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

Це робить генеру константу, fields_nяка є кількістю полів, що відбиваються в класі. Потім спеціалізується на field_dataкожній галузі. Він також дружить до reflectorкласу. Це так, що він може отримати доступ до полів, навіть якщо вони приватні:

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

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

Тепер для перегляду полів ми використовуємо шаблон відвідувача. Ми створюємо діапазон MPL від 0 до кількості полів і отримуємо доступ до даних полів у цьому індексі. Потім він передає дані про поле відвідувачу, який надає користувач:

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


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

Тепер на мить правди ми все це склали. Ось як ми можемо визначити Personклас, який відображається:

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

Ось узагальнена print_fieldsфункція, що використовує дані відображення для перегляду полів:

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

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

Приклад використання класу, що print_fieldsвідбивається Person:

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

Які виходи:

name=Tom
age=82

І вуаля, ми щойно реалізували відображення в C ++, під 100 рядками коду.


106
Кудо для того, щоб показати, як реалізувати рефлексію, а не сказати, що цього не можна зробити. Такі відповіді роблять SO чудовим ресурсом.
безстрашний_фул

4
Зауважте, що якщо ви спробуєте скомпілювати це під Visual Studio, ви отримаєте помилку, оскільки VS не працює належним чином для розширення варіативного макросу. Для VS спробуйте додати: #define DETAIL_TYPEOF_INT2(tuple) DETAIL_TYPEOF_HEAD tupleта #define DETAIL_TYPEOF_INT(...) DETAIL_TYPEOF_INT2((__VA_ARGS__)) змінити визначення TYPEOF (x) на:#define TYPEOF(x) DETAIL_TYPEOF_INT(DETAIL_TYPEOF_PROBE x,)
Phenglei Kai

Я отримую помилку "BOOST_PP_IIF_0" не називає тип. Чи можете ви допомогти.
Анкіт Залані

3
Дивіться мою власну відповідь - stackoverflow.com/a/28399807/2338477 Я вилучив і перепакував усі параметри, а бібліотека підвищення не потрібна. Як демо-код я надаю серіалізацію в xml та відновлення з xml.
ТармоПікаро

106

Є два види reflectionплавання.

  1. Перевірка шляхом ітерації над членами типу, перерахування його методів тощо.

    Це неможливо за допомогою C ++.
  2. Перевірка, перевіряючи, чи тип класу (клас, структура, об'єднання) має метод або вкладений тип, походить від іншого конкретного типу.

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

Якщо ви скоріше шукаєте способи виконання 1), як-от шукати, скільки методів має клас, або як отримати рядкове представлення ідентифікатора класу, то я боюся, що немає стандартного способу C ++. Ви повинні використовувати будь-яке

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

C ++ робиться з урахуванням швидкості. Якщо ви хочете перевірки на високому рівні, як, наприклад, C # або Java, я боюсь, що вам потрібно сказати, що без певних зусиль немає можливості.


122
C ++ робиться з урахуванням швидкості, але філософія не "настільки швидка, наскільки це можливо", натомість це "ви не платите за це, якщо не використовуєте його". Я вважаю, що мова може реалізувати інтроспекцію таким чином, що відповідає цій філософії, а C ++ її просто не вистачає.
Джозеф Гарвін

8
@ Джозеф: Як це робити? Для цього потрібно буде зберегти всі ці метадані. Що означає, що ви повинні заплатити за це, навіть якщо ви не користуєтесь ним. (Якщо тільки ви не можете позначити окремі типи як "підтримуючу рефлексію", але тоді ми майже внизу, де ми могли б також використати існуючий макро-хитрість.
jalf

25
@jalf: Тільки метадані, які можуть знадобитися. Якщо ми розглянемо лише роздуми за часом компіляції, це тривіально. Наприклад, функція часу компіляції, members<T>яка повертає список всіх членів T. Якби ми хотіли мати відображення часу виконання (тобто RTTI, змішане з відображенням), компілятор все-таки буде знати всі відображені базові типи. Цілком імовірно, members<T>(T&)що ніколи не буде примірник для T = std :: string, тому RTTI для std :: string або його похідні класи не потрібно включати.
MSalters

9
Бібліотека рефлексів (згадана нижче) додає рефлексію на C ++, не уповільнюючи існуючий код за адресою: root.cern.ch/drupal/content/reflex
Джозеф Лізе

6
@Joe: Рефлексія ніколи не сповільнює існуючий код. Це просто збільшує доставку матеріалів (оскільки вам доведеться доставити інформаційну базу даних про тип ...).
ммммммммм

56

І я б хотів поні, але поні не є безкоштовними. :-p

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


1
Я другий Бред. Шаблони C ++ можуть бути досить потужними, і існує багато досвіду щодо різних типів поведінки типу "відображення", таких як при збільшенні "будь-якої" бібліотеки, рисах типу, C ++ RTTI тощо, які можуть вирішити багато проблем, з яких вирішено. Отже, Нік, яка твоя мета тут?
Аарон

7
Підвищення за поні зауваження! Я б схвалив двічі, оскільки ваша відповідь також заслуговує цього, але, на жаль, я отримую лише один, тому поні виграють. :-)
Франці Пенов

6
Я насправді не розумію, чому це розумна відповідь. Я вже казав, що хотів би посилатися на бібліотеки тощо для цього. Роздум / самоаналіз призначений для різних систем, щоб дозволити доступ до сценарію, серіалізацію тощо
Нік

3
@Nick: Він уже відповів на це. Це неможливо зробити, дані не існують, а отже, жодна бібліотека не зможе їх реалізувати за вас.
jalf

@jalf Я все ще дивно читаю, що люди в світі програмування говорять, що "це не можливо", а не "я не знаю як". Впевнені, що метадані не існують, але їх можна вставити за допомогою макросів
Freddx L.

38

Інформація існує, але не у потрібному форматі, і лише якщо ви експортуєте свої класи. Це працює в Windows, я не знаю про інші платформи. Використовуючи специфікатори класу зберігання, як у, наприклад:

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

Це змушує компілятор збирати дані визначення класу в DLL / Exe. Але це не у форматі, який ви можете легко використовувати для роздумів.

У моїй компанії ми створили бібліотеку, яка інтерпретує ці метадані та дозволяє відображати клас, не вставляючи зайві макроси тощо у сам клас. Це дозволяє викликати функції таким чином:

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

Це ефективно робить:

instance_ptr->Foo(1.331);

Функція Invoke (this_pointer, ...) має змінні аргументи. Очевидно, викликуючи функцію таким чином, ви обминаєте такі речі, як const-security тощо, тому ці аспекти реалізуються як перевірки часу виконання.

Я впевнений, що синтаксис можна було б покращити, і він працює лише на Win32 та Win64. Ми вважаємо, що це дуже корисно для автоматичних інтерфейсів графічного інтерфейсу для класів, створення властивостей у C ++, потокової передачі до XML і т. Д., І не потрібно виходити з конкретного базового класу. Якщо є достатньо попиту, можливо, ми могли б привести його у форму для випуску.


1
Я думаю, ви маєте на увазі, __declspec(dllexport)і ви можете отримати інформацію з файлу .map, якщо увімкнути створення таких під час збирання.
Орвелофіл

18

Відображення не підтримується C ++ поза коробкою. Це сумно, тому що це робить захисне тестування болем.

Існує кілька підходів до роздумів:

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

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

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

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

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

Зараз існує робоча група з роздумів C ++. Дивіться новини для C ++ 14 @ CERN:

Редагувати 13.08.17:

З часу початкового допису було розроблено низку потенційних зрушень щодо роздумів. Далі подано більш детальну інформацію та обговорення різних методів та статусу:

  1. Статичне відображення в горішці
  2. Статична рефлексія
  3. Конструкція для статичного відображення

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

Нижче наведено детальний опис поточного стану на основі відгуків з останньої зустрічі стандартів C ++:

Редагувати 13.12.2017

Відображення, схоже, рухається до C ++ 20 або більше, ймовірно, TSR. Проте рух повільний.

Редагувати 15.09.2018

Проект ТС був направлений національним органам для голосування.

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

Редагувати 07.11.2019

TS-рефлексія є повноцінною і віддається коментарям та голосуванню протягом літа (2019).

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

Редагувати 02.02.2020

Тут є запит на підтримку TS відображення у Visual Studio:

Розмова на ТС автора Девіда Санкеля:

Редагувати 17 березня 2020 року

Досягається прогрес у роздумах. Звіт із „Звіту про поїздку комітету ISO C ++ за 2020-02 р.“ Можна знайти тут:

Докладніше про те, що розглядається для C ++ 23, можна знайти тут (включає короткий розділ про відображення):

Редагувати 4 червня 2020 року

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

Інструменти та підхід виглядають найбільш відшліфованими та найпростішими у використанні досі.


1
Цернну ланку розірвано.
Mostowski Згорнутись

cern-ланки мають бути виправлені зараз. Вони, як правило, ламаються досить часто, що є болем.
Даміан Діксон

Чи стосується ця відповідь лише роздум про час збирання?
einpoklum

@einpoklum єдиними поточними рішеннями для роздумів є час компіляції, як правило, з кодом мета-шаблону або макрокомандами. Останній проект TS виглядає так, що він повинен працювати під час виконання, але вам доведеться створити всі бібліотеки з правильним компілятором, щоб необхідні метадані були збережені.
Даміан Діксон

@DamianDixon: Це неправда. Є кілька бібліотек відображення часу виконання. Тепер, на жаль, вони досить незграбні і або відмовляються, або вимагають нодифікацій компілятора, але вони все ще існують. Якщо, як я розумію ваш коментар, ви посилалися лише на роздуми під час збирання, будь ласка, відредагуйте свою відповідь, щоб зробити її зрозумілішою.
einpoklum

15

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

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

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

Перший виклик додає цей об'єкт до системи фільтрації, яка викликає BuildMap()метод, щоб з'ясувати, які методи доступні.

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

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

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


треба любити ці функції, які завжди повертаються правдою;) Я припускаю, що це захищено від статичних проблем із замовленням?
paulm

14

Я рекомендую використовувати Qt .

Існує ліцензія з відкритим кодом, а також комерційна ліцензія.


1
Я переглянув це, але він використовує макроси, і вихідний код потребує розбору для створення коду метаданих. Я хотів би уникнути цього додаткового кроку. Я вважаю за краще використовувати бібліотеку C ++ або прості макроси. Дякую за ідею, хоча.
Нік

10
QT або інша бібліотека, що реалізує подібний підхід - найкраще, що ви збираєтеся отримати
jalf

5
Оплачуйте під час компіляції або платіть під час виконання - будь-яким способом ви платите!
Мартін Бекетт

13

Що ви намагаєтеся зробити з роздумом?
Ви можете використовувати риси типу Boost та бібліотеки typeof як обмежену форму відображення часу компіляції. Тобто ви можете перевірити та змінити основні властивості типу, переданого шаблону.


13

EDIT : CAMP більше не підтримується; доступні дві виделки:

  • Один також називається CAMP і базується на тому ж API.
  • Роздум - це часткове перезапис, і він буде віддавати перевагу, оскільки він не вимагає підвищення; він використовує C ++ 11.

CAMP - бібліотека, що має ліцензію MIT (раніше LGPL), яка додає роздуми до мови C ++. Для компіляції це не вимагає певного кроку попередньої обробки, але прив'язка повинна бути виконана вручну.

Поточна бібліотека Tegesoft використовує Boost, але також існує вилка, що використовує C ++ 11, яка більше не потребує Boost .


11

Я робив щось на кшталт того, що ти маєш після одного разу, і хоча можливо отримати певний рівень роздумів та доступ до функцій вищого рівня, головний біль у технічному обслуговуванні може не вартий цього. Мою систему використовували для того, щоб класи інтерфейсу користувалися повністю відокремленими від ділової логіки шляхом делегування, схожого на концепцію передачі та переадресації об'єктива-С об'єктива-С. Спосіб зробити це - створити базовий клас, здатний відображати символи (я використовував рядок рядків, але ви могли це зробити з перерахунками, якщо ви віддаєте перевагу швидкість та обробку помилок під час компіляції над повною гнучкістю) для функціонування покажчиків (насправді ні чисті покажчики функцій, але щось подібне до того, що Boost має у Boost.Function - до якого я тоді не мав доступу). Ви можете зробити те ж саме для змінних вашого члена, якщо у вас є якийсь загальний базовий клас, здатний представляти будь-яке значення. Уся система являла собою непередбачуваний стрибок кодування та делегування ключових значень, маючи кілька побічних ефектів, які, можливо, коштували часу, необхідного для отримання кожного класу, який використовував систему, щоб відповідати всім її методам та членам юридичних викликів : 1) Будь-який клас може викликати будь-який метод будь-якого іншого класу без необхідності включати заголовки або записувати підроблені базові класи, щоб інтерфейс міг бути заздалегідь визначений для компілятора; та 2) Геттери та задачі змінних членів легко зробити безпечними для потоків, оскільки зміна або доступ до їх значень завжди робився за допомогою двох методів у базовому класі всіх об'єктів. Уся система являла собою непередбачуваний стрибок кодування та делегування ключових значень, маючи кілька побічних ефектів, які, можливо, коштували часу, необхідного для отримання кожного класу, який використовував систему, щоб відповідати всім її методам та членам юридичних викликів : 1) Будь-який клас може викликати будь-який метод будь-якого іншого класу без необхідності включати заголовки або записувати підроблені базові класи, щоб інтерфейс міг бути заздалегідь визначений для компілятора; та 2) Геттери та задачі змінних членів легко зробити безпечними для потоків, оскільки зміна або доступ до їх значень завжди робився за допомогою двох методів у базовому класі всіх об'єктів. Вся система являла собою непередбачуваний стрибок кодування та делегування ключових значень, з кількома побічними ефектами, які, можливо, варті великої кількості часу, необхідного для отримання кожного класу, який використовував систему, щоб відповідати всім її методам та членам юридичних викликів : 1) Будь-який клас може викликати будь-який метод будь-якого іншого класу без необхідності включати заголовки або записувати підроблені базові класи, щоб інтерфейс міг бути заздалегідь визначений для компілятора; та 2) Геттери та задачі змінних членів легко зробити безпечними для потоків, оскільки зміна або доступ до їх значень завжди робився за допомогою двох методів у базовому класі всіх об'єктів. 1) Будь-який клас може викликати будь-який метод будь-якого іншого класу без включення заголовків або запису підроблених базових класів, щоб інтерфейс міг бути заздалегідь визначений для компілятора; та 2) Геттери та задачі змінних членів легко зробити безпечними для потоків, оскільки зміна або доступ до їх значень завжди робився за допомогою двох методів у базовому класі всіх об'єктів. 1) Будь-який клас може викликати будь-який метод будь-якого іншого класу без включення заголовків або запису підроблених базових класів, щоб інтерфейс міг бути заздалегідь визначений для компілятора; та 2) Геттери та задачі змінних членів легко зробити безпечними для потоків, оскільки зміна або доступ до їх значень завжди робився за допомогою двох методів у базовому класі всіх об'єктів.

Це також призвело до можливості робити деякі по-справжньому дивні речі, які в іншому випадку є непростими в C ++. Наприклад, я міг створити об’єкт Array, який містив довільні елементи будь-якого типу, включаючи себе, та створювати нові масиви динамічно, передаючи повідомлення всім елементам масиву та збираючи зворотні значення (аналогічно карті у Lisp). Іншою була реалізація спостереження за значенням ключових значень, завдяки якому я мав можливість налаштувати інтерфейс користувача негайно реагувати на зміни членів бекенд-класів замість того, щоб постійно опитувати дані або зайве перемальовувати дисплей.

Можливо, вам цікавіше те, що ви також можете скидати всі методи та члени, визначені для класу, і в рядковій формі не менше.

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

Щодо того, як реалізувати щось подібне: просто використовуйте спільні та слабкі вказівники на якусь загальну базу (мою дуже образно називали "Об'єкт") та отримуйте для всіх типів, які ви хочете використовувати. Я б рекомендував встановити Boost.Function, а не робити це так, як я це робив, який мав певне лайно та тонну некрасивих макросів, щоб обернути виклики вказівника функції. Оскільки все відображено, огляд об’єктів - це лише питання перегляду всіх клавіш. Оскільки мої заняття по суті були максимально наближеними до прямої зрілості какао, використовуючи лише C ++, якщо ви хочете чогось подібного, то я б запропонував використовувати документацію на какао як креслення.


Гей, @Michael; у вас все ще є вихідний код для цього, або ви його позбулися? Я хотів би поглянути на це, якщо ви не заперечуєте.
RandomDSdevel

Ну, неправильно написано ваше ім’я! Чи не дивно , що я ніколи не отримав відповідь ...
RandomDSdevel

10

Існує ще одна нова бібліотека для відображення в C ++, яка називається RTTR (Reflection Type Type Reflection, див. Також github ).

Інтерфейс схожий на відображення в C # і працює без RTTI.


8

Два рішення, схожі на роздуми, про які я знаю з моїх днів C ++:

1) Використовуйте RTTI, який забезпечить вам завантажувальну систему для побудови поведінки, подібної до рефлексії, якщо ви зможете змусити всі ваші класи виходити з базового класу "об'єкт". Цей клас може надати деякі методи, такі як GetMethod, GetBaseClass тощо. Щодо того, як ці методи працюють, вам потрібно буде вручну додати кілька макросів, щоб прикрасити ваші типи, які за лаштунками створюють метадані типу, щоб надати відповіді на GetMethods тощо.

2) Ще одним варіантом, якщо у вас є доступ до об'єктів компілятора, є використання DIA SDK . Якщо я правильно пам'ятаю, це дозволяє відкривати pdbs, які мають містити метадані для ваших типів C ++. Можливо, буде достатньо зробити те, що потрібно. Ця сторінка показано, як можна отримати, наприклад, всі базові типи класу.

Обидва ці рішення трохи некрасиві! Немає нічого, як трохи C ++, щоб не оцінити розкіш C #.

Щасти.


Це хитрий і велетенський хакер, з DIA SDK, що ви запропонували там.
Sqeaky

7

EDIT: Оновлено розірване посилання станом на 7 лютого 2017 року.

Я думаю, що про це ніхто не згадував:

У CERN вони використовують повну систему відображення для C ++:

CERN Reflex . Здається, це працює дуже добре.


@ j4nbur53 Посилання розірвана, тому що, здається, вони досягли важливої ​​віхи: root.cern.ch
Герман

Можливо, ви маєте на увазі це посилання root.cern.ch/root/doc/ROOTUsersGuideHTML/ch07.html Розділ рефлексу ?
Mostowski Згорнутись

Спробуйте цей root.cern.ch/how/how-use-reflex . Reflex працює як генератор, який аналізує файли заголовків і генерує код + бібліотеку інтроспекції c ++, з якою ви можете зв’язати та використовувати простий api.
Адам Річковський

6

Зараз це питання трохи старе (не знаю, чому я продовжую вражати старі питання сьогодні), але я думав про BOOST_FUSION_ADAPT_STRUCT, який представляє час відображення часу компіляції.

Ви, мабуть, відобразити це, звичайно, на час відображення часу, і це буде не надто просто, але це можливо в цьому напрямку, хоча це не буде в зворотному напрямку :)

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


2
автор minghua (який спочатку редагував публікацію): я заглибився у це рішення BOOST_FUSION_ADAPT_STRUCT і врешті-решт придумав приклад. Дивіться це нове запитання SO - ітерацію C ++ у вкладене поле структури з підсиленням fusion adapt_struct .
Матьє М.

Чудово, Матьє! Щойно зрозумів, побачивши свої підказки тут і там протягом минулого року. Не помітили, що вони до цього часу є спорідненими. Це було дуже натхненно.
minghua

6

Думаю, вам може бути цікавою стаття «Використання шаблонів для відображення в C ++» Домініка Філіона. Він знаходиться в розділі 1.4 ігрових коштовностей 5 . На жаль, у мене немає своєї копії, але я шукаю її, тому що я думаю, це пояснює те, що ви просите.


4

Ponder - це бібліотека роздумів C ++, відповідь на це запитання. Я розглядав варіанти і вирішив зробити свій власний, оскільки не зміг знайти жодного, який поставив би всі мої скриньки.

Хоча на це питання є чудові відповіді, я не хочу використовувати тонни макросів або покладатися на Boost. Boost - це чудова бібліотека, але є багато невеликих замовлень C ++ 0x проектів, які є більш простими та швидшими. Також є переваги в тому, що можна прикрасити клас зовні, як упаковка бібліотеки C ++, яка не підтримує C ++ 11. Болтинг CAMP, що використовує C ++ 11, більше не вимагає підвищення .


4

Рефлексія по суті полягає в тому, що компілятор вирішив залишити як сліди в коді, до якого можна виконати запит коду виконання. C ++ відомий тим, що не платить за те, що не використовуєш; оскільки більшість людей не використовують / хочуть відображення, компілятор C ++ уникає витрат, нічого не записуючи .

Таким чином, C ++ не дає рефлексії, і непросто "моделювати" його самостійно, як загальне правило, як відзначали інші відповіді.

Під "іншими методами", якщо у вас немає мови з відображенням, знайдіть інструмент, який може витягти потрібну інформацію під час компіляції.

Наш інструментарій для реінжинірингу програмного забезпечення DMS - це узагальнена технологія компілятора, параметризована явними визначеннями мови. Він має визначення мови для C, C ++, Java, COBOL, PHP, ...

Для версій C, C ++, Java та COBOL він забезпечує повний доступ до розбору дерев та інформації таблиці символів. Інформація про таблицю символів містить тип даних, які ви, ймовірно, хочете отримати з "відображення". Якщо ваша мета - перерахувати деякий набір полів або методів і зробити щось з ними, DMS може бути використаний для перетворення коду відповідно до того, що ви знайдете в таблицях символів довільними способами.


3

Ви можете знайти іншу бібліотеку тут: http://www.garret.ru/cppreflection/docs/reflect.html Він підтримує 2 способи: отримати інформацію про тип від інформації про налагодження та дозволити програмісту надати цю інформацію.

Я також зацікавився роздумом свого проекту і знайшов цю бібліотеку, я ще не пробував її, але спробував інші інструменти від цього хлопця, і мені подобається, як вони працюють :-)


3

Перевірте Classdesc http://classdesc.sf.net . Він надає відображення у вигляді "дескрипторів" класу, працює з будь-яким стандартним компілятором C ++ (так, як відомо, працює з Visual Studio, а також з GCC), і не вимагає анотації вихідного коду (хоча для працездатних ситуацій існують деякі прагми) ). Він розробляється вже більше десяти років і використовується у ряді проектів промислового масштабу.


1
Ласкаво просимо до переповнення стека. Хоча ця відповідь є темою, важливо зазначити, що ви є автором цього програмного забезпечення, щоб зрозуміти, що це не об'єктивна рекомендація :-)
Метью Штрабрідж

2

Коли я хотів роздумів на C ++, я прочитав цю статтю і вдосконалив те, що там побачив. Вибачте, не може. Я не володію результатом ... але ви, звичайно, можете отримати те, що я мав, і піти звідти.

В даний час я досліджую, коли мені це подобається, методи використовувати nasledit_linearly, щоб значно полегшити визначення типів відбиття. Насправді я досить далеко зайшов, але в мене все ще є шляхи. Зміни в C ++ 0x, ймовірно, можуть допомогти в цій галузі.


2

Схоже, C ++ досі не має цієї функції. І C ++ 11 відкладене відображення теж ((

Шукайте кілька макросів або робіть власні. Qt також може допомогти у відображенні (якщо його можна використовувати).


2

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

стаття дуже докладно пояснює, як можна реалізувати досить просту і рудиментарну систему відображення. надав його не найкорисніше рішення, і для розбирання залишилося шорсткі краї, але для моїх потреб це було достатньо.

підсумок - відображення може окупитися, якщо виконано правильно, і це цілком можливо в c ++.


2

Я хотів би оголосити про існування автоматичного інструментарію інтроспекції / рефлексії "IDK". Він використовує метакомпілятор на зразок Qt і додає метаінформацію безпосередньо у файли об'єктів. Він, як стверджується, простий у використанні. Ніяких зовнішніх залежностей. Він навіть дозволяє автоматично відображати std :: string і потім використовувати його в скриптах. Будь ласка, подивіться на IDK


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 для отримання демо-коду в 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://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.

Але оскільки тема дуже просунута, то рекомендую спочатку переглянути відео.

Якщо у вас є якісь ідеї щодо вдосконалення, не соромтесь зв’язатися зі мною.


1

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

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

Де ENUMERATE_MEMBERS - це макрос, який буде описано пізніше (UPDATE):

Припустимо, ми визначили функцію серіалізації для int та std :: string, як це:

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

І у нас є родова функція біля "секретного макросу";)

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

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

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

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

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

Цей макрос повинен мати нульові накладні витрати для створення / знищення структури під час виконання, і код T.EnumerateWith () повинен генеруватися на вимогу, чого можна досягти, зробивши його функцією шаблону вбудованою, тому єдиним накладним в вся історія полягає в тому, щоб до кожної структури додати ENUMERATE_MEMBERS (м1, м2, м3 ...), в той час як реалізація конкретного методу на тип члена є обов'язковим у будь-якому рішенні, тому я не вважаю це за накладні витрати.

ОНОВЛЕННЯ: Існує дуже проста реалізація макросу 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 рядків коду вам не потрібна жодна стороння бібліотека;)


1

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


1

Відображення довільного доступу бібліотека робить досить простий і інтуїтивне відображення - все інформаційне поле / типу призначена або бути доступні в масивах або відчувати себе доступ до масиву. Він написаний для C ++ 17 і працює з Visual Studios, g ++ та Clang. Бібліотека має лише заголовок, тобто для її використання потрібно лише скопіювати "Reflect.h".

Відображеним структурам або класам потрібен макрос REFLECT, де ви надаєте ім'я відображеного вами класу та назви полів.

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

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

Це все, що немає, додатковий код не потрібен для налаштування відображення. За бажанням ви можете надати суперкласи (у дужках першого аргументу) та анотації на полях (у круглих дужках, що передують полі, яке ви хочете анотувати), щоб мати можливість переходити до суперклассів або додавати до поля додаткову інформацію про час збирання (наприклад, Json: : Ігнорувати).

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

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

Ви можете переходити через екземпляр об'єкта для доступу до значень поля (які ви можете читати чи змінювати) та інформації про тип поля ...

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

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

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

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

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

Вище сказане можна було б прокласти так ...

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


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

Дивитися також...


0

Якщо ви оголосите вказівник на таку функцію:

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

Ви можете призначити місце в пам'яті цій функції, як ця (потрібно libdlі dlopen)

#include <dlfcn.h>

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

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

Єдина вимога до цього (крім dlopen(), libdlі dlfcn.h) - це знати аргументи та тип функції.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.