Как я могу добавить отражение в приложение C ++? - PullRequest
228 голосов
/ 03 сентября 2008

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

Ответы [ 32 ]

2 голосов
/ 12 марта 2014

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

2 голосов
/ 14 июня 2010

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

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

2 голосов
/ 03 февраля 2013

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

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

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

2 голосов
/ 24 ноября 2011

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

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

2 голосов
/ 17 февраля 2012

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

1 голос
/ 26 марта 2019

Если вы ищете относительно простое отражение 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 define использует имя класса + имя поля с 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

Я пытаюсь немного глубже объяснить, как будет работать отражение 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 голос
/ 22 марта 2019

С 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>();
}
1 голос
/ 08 ноября 2016

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

UPDATE: Существует очень простая реализация макроса ENUMERATE_MEMBERS (однако его можно немного расширить для поддержки наследования из перечисляемой структуры)

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

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

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

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

0 голосов
/ 03 марта 2019

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

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) - знание аргументов и типа функции.

0 голосов
/ 17 июня 2016

Проект Root Reflex поддерживает это.

См. https://root.cern.ch/how/how-use-reflex

...