Pergunta

Eu gostaria de poder examinar uma classe C++ quanto ao seu nome, conteúdo (ou seja,membros e seus tipos) etc.Estou falando de C++ nativo aqui, não de C++ gerenciado, que tem reflexão.Sei que o C++ fornece algumas informações limitadas usando o RTTI.Quais bibliotecas adicionais (ou outras técnicas) poderiam fornecer esta informação?

Foi útil?

Solução 17

Ponderar é uma biblioteca de reflexão C++, em resposta a esta pergunta.Considerei as opções e decidi fazer a minha própria, pois não consegui encontrar uma que atendesse a todas as minhas caixas.

Embora existam ótimas respostas para essa pergunta, não quero usar toneladas de macros nem confiar no Boost.Boost é uma ótima biblioteca, mas existem muitos pequenos projetos C++ 0x personalizados que são mais simples e têm tempos de compilação mais rápidos.Também há vantagens em poder decorar uma classe externamente, como agrupar uma biblioteca C++ que (ainda?) não suporta C++11.É um fork do CAMP, usando C++11, que não requer mais Boost.

Outras dicas

O que você precisa fazer é fazer com que o pré-processador gere dados de reflexão sobre os campos.Esses dados podem ser armazenados como classes aninhadas.

Primeiro, para tornar mais fácil e limpo escrevê-lo no pré-processador, usaremos expressão digitada.Uma expressão digitada é apenas uma expressão que coloca o tipo entre parênteses.Então, em vez de escrever int x você vai escrever (int) x.Aqui estão algumas macros úteis para ajudar com expressões digitadas:

#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

A seguir, definimos um REFLECTABLE macro para gerar os dados sobre cada campo (mais o próprio campo).Esta macro será chamada assim:

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

Então, usando Impulsionar.PP iteramos sobre cada argumento e geramos os dados assim:

// 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)); \
    } \
}; \

O que isso faz é gerar uma constante fields_n esse é o número de campos refletidos na classe.Depois especializa o field_data para cada campo.Também é amigo do reflector class, isso é para que ele possa acessar os campos mesmo quando eles são privados:

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;
    };
};

Agora, para iterar nos campos, usamos o padrão de visitante.Criamos um intervalo MPL de 0 ao número de campos e acessamos os dados do campo nesse índice.Em seguida, ele passa os dados do campo para o visitante fornecido pelo usuário:

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));
}

Agora, na hora da verdade, juntamos tudo.Aqui está como podemos definir um Person classe que é refletida:

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

Aqui está uma generalização print_fields função usando os dados de reflexão para iterar sobre os campos:

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());
}

Um exemplo de uso do print_fields com o refletivo Person aula:

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

Quais saídas:

name=Tom
age=82

E pronto, acabamos de implementar a reflexão em C++, em menos de 100 linhas de código.

Existem dois tipos de reflection nadando por aí.

  1. Inspeção iterando membros de um tipo, enumerando seus métodos e assim por diante.

    Isso não é possível com C++.
  2. A inspeção verificando se um tipo de classe (classe, estrutura, união) possui um método ou tipo aninhado é derivado de outro tipo específico.

    Esse tipo de coisa é possível com C++ usando template-tricks.Usar boost::type_traits para muitas coisas (como verificar se um tipo é integral).Para verificar a existência de uma função membro, use É possível escrever um modelo para verificar a existência de uma função? .Para verificar se existe um determinado tipo aninhado, use simples SFINAE .

Se você está procurando maneiras de realizar 1), como ver quantos métodos uma classe possui ou obter a representação de string de um ID de classe, receio que não exista uma maneira C++ padrão de fazer isso.Você tem que usar qualquer um

  • Um Meta Compiler como o Qt Meta Object Compiler que traduz seu código adicionando meta informações adicionais.
  • Um Framework composto por macros que permitem adicionar as meta-informações necessárias.Você precisaria informar ao framework todos os métodos, os nomes das classes, classes base e tudo o que ele precisa.

C++ é feito pensando na velocidade.Se você deseja uma inspeção de alto nível, como C # ou Java, tenho que dizer que não há como sem algum esforço.

E eu adoraria um pônei, mas pôneis não são de graça.:-p

http://en.wikibooks.org/wiki/C%2B%2B_Programming/RTTI é o que você vai conseguir.A reflexão como você está pensando - metadados totalmente descritivos disponíveis em tempo de execução - simplesmente não existe para C++ por padrão.

RTTI não existe para C++.

Isto está simplesmente errado.Na verdade, o próprio termo “RTTI” foi cunhado pelo padrão C++.Por outro lado, a RTTI não vai muito longe na implementação da reflexão.

A informação existe - mas não no formato que você precisa, e somente se você exportar suas aulas.Isso funciona no Windows, não conheço outras plataformas.Usando os especificadores de classe de armazenamento como, por exemplo:

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

Isso faz com que o compilador construa os dados de definição de classe no DLL/Exe.Mas não está em um formato que você possa usar facilmente para reflexão.

Na minha empresa construímos uma biblioteca que interpreta esses metadados e permite refletir uma classe sem inserir macros extras, etc.na própria classe.Ele permite que funções sejam chamadas da seguinte forma:

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

Isso efetivamente faz:

instance_ptr->Foo(1.331);

A função Invoke(this_pointer,...) possui argumentos variáveis.Obviamente, ao chamar uma função dessa maneira, você está contornando coisas como const-safety e assim por diante, portanto, esses aspectos são implementados como verificações de tempo de execução.

Tenho certeza de que a sintaxe poderia ser melhorada e até agora só funciona em Win32 e Win64.Achamos que é realmente útil ter interfaces GUI automáticas para classes, criar propriedades em C++, transmitir de e para XML e assim por diante, e não há necessidade de derivar de uma classe base específica.Se houver demanda suficiente, talvez possamos colocá-lo em forma para lançamento.

Você precisa analisar o que está tentando fazer e se o RTTI atenderá às suas necessidades.Implementei minha própria pseudo-reflexão para alguns propósitos muito específicos.Por exemplo, uma vez eu quis ser capaz de configurar de forma flexível o resultado de uma simulação.Foi necessário adicionar algum código padrão às classes que seriam geradas:

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;
}

A primeira chamada adiciona esse objeto ao sistema de filtragem, que chama o BuildMap() método para descobrir quais métodos estão disponíveis.

Então, no arquivo de configuração, você pode fazer algo assim:

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

Através de alguma magia de modelo envolvendo boost, isso é traduzido em uma série de chamadas de método em tempo de execução (quando o arquivo de configuração é lido), portanto é bastante eficiente.Eu não recomendaria fazer isso a menos que você realmente precise, mas, quando fizer isso, você poderá fazer coisas muito legais.

O que você está tentando fazer com a reflexão?
Você pode usar o impulso características de tipo e tipo de bibliotecas como uma forma limitada de reflexão em tempo de compilação.Ou seja, você pode inspecionar e modificar as propriedades básicas de um tipo passado para um modelo.

Eu recomendaria usar Qt.

Existe uma licença de código aberto, bem como uma licença comercial.

EDITAR: ACAMPAMENTO não é mais mantido;dois garfos estão disponíveis:

  • Um também é chamado ACAMPAMENTO também e é baseado na mesma API.
  • Ponderar é uma reescrita parcial e deve ser preferida porque não requer Boost ;está usando C++ 11.

ACAMPAMENTO é uma biblioteca licenciada pelo MIT (anteriormente LGPL) que adiciona reflexão à linguagem C++.Não requer uma etapa específica de pré-processamento na compilação, mas a vinculação deve ser feita manualmente.

A biblioteca atual da Tegesoft usa Boost, mas também existe um garfo usando C++ 11 que não requer mais Boost.

Uma vez fiz algo parecido com o que você procura e, embora seja possível obter algum nível de reflexão e acesso a recursos de nível superior, a dor de cabeça da manutenção pode não valer a pena.Meu sistema foi usado para manter as classes de UI completamente separadas da lógica de negócios por meio de delegação semelhante ao conceito de passagem e encaminhamento de mensagens do Objective-C.A maneira de fazer isso é criar alguma classe base que seja capaz de mapear símbolos (usei um conjunto de strings, mas você poderia fazer isso com enums se preferir velocidade e tratamento de erros em tempo de compilação em vez de flexibilidade total) para ponteiros de função (na verdade não ponteiros de função pura, mas algo semelhante ao que Boost tem com Boost.Function - ao qual eu não tinha acesso na época).Você pode fazer o mesmo com suas variáveis-membro, desde que tenha alguma classe base comum capaz de representar qualquer valor.Todo o sistema era uma imitação descarada de codificação e delegação de valores-chave, com alguns efeitos colaterais que talvez valessem a quantidade de tempo necessária para fazer com que cada classe que usava o sistema combinasse todos os seus métodos e membros com chamadas legais. :1) Qualquer classe pode chamar qualquer método em qualquer outra classe sem precisar incluir cabeçalhos ou escrever classes base falsas para que a interface possa ser predefinida para o compilador;e 2) Os getters e setters das variáveis-membro eram fáceis de tornar thread-safe porque a alteração ou acesso aos seus valores sempre era feito através de 2 métodos na classe base de todos os objetos.

Também levou à possibilidade de fazer algumas coisas realmente estranhas que de outra forma não seriam fáceis em C++.Por exemplo, eu poderia criar um objeto Array que contivesse itens arbitrários de qualquer tipo, incluindo ele mesmo, e criar novos arrays dinamicamente, passando uma mensagem para todos os itens do array e coletando os valores de retorno (semelhante ao mapa em Lisp).Outra foi a implementação da observação de valores-chave, por meio da qual consegui configurar a IU para responder imediatamente às alterações nos membros das classes de back-end, em vez de pesquisar constantemente os dados ou redesenhar desnecessariamente a exibição.

Talvez mais interessante para você seja o fato de que você também pode despejar todos os métodos e membros definidos para uma classe, e nada menos em forma de string.

Desvantagens do sistema que podem desencorajá-lo de se preocupar:adicionar todas as mensagens e valores-chave é extremamente tedioso;é mais lento do que sem qualquer reflexão;você vai passar a odiar ver boost::static_pointer_cast e boost::dynamic_pointer_cast em toda a sua base de código com uma paixão violenta;as limitações do sistema fortemente tipado ainda estão lá, você está apenas escondendo-as um pouco, então não é tão óbvio.Erros de digitação em suas strings também não são uma surpresa divertida ou fácil de descobrir.

Sobre como implementar algo assim:basta usar ponteiros compartilhados e fracos para alguma base comum (o meu foi chamado de forma muito imaginativa de "Objeto") e derivar para todos os tipos que você deseja usar.Eu recomendo instalar o Boost.Function em vez de fazer do jeito que fiz, que foi com algumas porcarias personalizadas e uma tonelada de macros feias para agrupar as chamadas do ponteiro de função.Como tudo está mapeado, inspecionar objetos é apenas uma questão de iterar todas as chaves.Como minhas aulas eram essencialmente o mais próximo possível de uma cópia direta do Cocoa usando apenas C++, se você quiser algo assim, sugiro usar a documentação do Cocoa como modelo.

As duas soluções semelhantes a reflexão que conheço desde meus dias de C++ são:

1) Use o RTTI, que fornecerá um bootstrap para você construir seu comportamento semelhante ao de reflexão, se você conseguir que todas as suas classes derivem de uma classe base de 'objeto'.Essa classe poderia fornecer alguns métodos como GetMethod, GetBaseClass etc.Quanto ao funcionamento desses métodos, você precisará adicionar manualmente algumas macros para decorar seus tipos, que nos bastidores criam metadados no tipo para fornecer respostas a GetMethods etc.

2) Outra opção, caso você tenha acesso aos objetos do compilador é utilizar o DIASDK.Se bem me lembro, isso permite abrir pdbs, que deve conter metadados para seus tipos C++.Pode ser o suficiente para fazer o que você precisa. Esta página mostra como você pode obter todos os tipos base de uma classe, por exemplo.

Ambas as soluções são um pouco feias!Não há nada como um pouco de C++ para fazer você apreciar os luxos do C#.

Boa sorte.

Existe outra nova biblioteca para reflexão em C++, chamada RTTR (Run Time Type Reflection, veja também GitHub).

A interface é semelhante à reflexão em C# e funciona sem nenhum RTTI.

EDITAR:Link quebrado atualizado em 7 de fevereiro de 2017.

Acho que ninguém mencionou isso:

No CERN eles usam um sistema de reflexão completo para C++:

Reflexo do CERN.Isso parece funcionar muito bem.

O Reflection não é compatível com C++ pronto para uso.Isso é triste porque torna os testes defensivos uma dor.

Existem várias abordagens para fazer reflexão:

  1. use as informações de depuração (não portáteis).
  2. Polvilhe seu código com macros/modelos ou alguma outra abordagem de origem (parece feio)
  3. Modifique um compilador como clang/gcc para produzir um banco de dados.
  4. Use a abordagem Qt moc
  5. Aumentar a reflexão
  6. Reflexão precisa e plana

O primeiro link parece o mais promissor (usa mods para clang), o segundo discute uma série de técnicas, o terceiro é uma abordagem diferente usando o gcc:

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

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

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

Existe agora um grupo de trabalho para reflexão em C++.Veja as novidades para C++14 @ CERN:

Editar 13/08/17:Desde a postagem original, houve uma série de avanços potenciais na reflexão.O seguinte fornece mais detalhes e uma discussão sobre as diversas técnicas e status:

  1. Reflexão estática em poucas palavras
  2. Reflexão Estática
  3. Um design para reflexão estática

No entanto, não parece promissor uma abordagem de reflexão padronizada em C++ num futuro próximo, a menos que haja muito mais interesse da comunidade no apoio à reflexão em C++.

A seguir detalhamos o status atual com base no feedback da última reunião de padrões C++:

Editar 13/12/2017

A reflexão parece estar caminhando para o C++ 20 ou mais provavelmente um TSR.O movimento é, no entanto, lento.

Editar 15/09/2018

Um projecto de TS foi enviado aos organismos nacionais para votação.

O texto pode ser encontrado aqui: https://github.com/cplusplus/reflection-ts

Esta pergunta é um pouco antiga agora (não sei por que continuo respondendo a perguntas antigas hoje), mas estava pensando sobre BOOST_FUSION_ADAPT_STRUCT que introduz reflexão em tempo de compilação.

Depende de você mapear isso para a reflexão em tempo de execução, é claro, e não será muito fácil, mas é possível nessa direção, embora não seja o contrário :)

Eu realmente acho que uma macro para encapsular o BOOST_FUSION_ADAPT_STRUCT pode-se gerar os métodos necessários para obter o comportamento do tempo de execução.

Acho que você pode achar interessante o artigo "Usando modelos para reflexão em C++" de Dominic Filion.Está na seção 1.4 do Gemas de programação de jogos 5.Infelizmente não tenho minha cópia comigo, mas procure porque acho que explica o que você está pedindo.

A reflexão é essencialmente sobre o que o compilador decidiu deixar como pegadas no código que o código de tempo de execução pode consultar.C++ é famoso por não pagar pelo que você não usa;como a maioria das pessoas não usa/quer reflexão, o compilador C++ evita o custo ao não registrar qualquer coisa.

Portanto, o C++ não fornece reflexão e não é fácil "simular" você mesmo como regra geral, como outras respostas observaram.

Em "outras técnicas", se você não possui uma linguagem com reflexão, obtenha uma ferramenta que possa extrair as informações desejadas em tempo de compilação.

Nosso Kit de ferramentas de reengenharia de software DMS é uma tecnologia de compilador generalizada parametrizada por definições de linguagem explícitas.Possui definições de linguagem para C, C++, Java, COBOL, PHP, ...

Para versões C, C++, Java e COBOL, fornece acesso completo para analisar árvores e informações da tabela de símbolos.Essas informações da tabela de símbolos incluem o tipo de dados que você provavelmente desejará da "reflexão".Se seu objetivo é enumerar algum conjunto de campos ou métodos e fazer algo com eles, o DMS pode ser usado para transformar o código de acordo com o que você encontra nas tabelas de símbolos de maneiras arbitrárias.

Você pode encontrar outra biblioteca aqui: http://www.garret.ru/cppreflection/docs/reflect.htmlSuporta 2 maneiras:obter informações de tipo de informações de depuração e permitir que o programador forneça essas informações.

Também me interessei pela reflexão para o meu projeto e encontrei essa biblioteca, ainda não experimentei, mas experimentei outras ferramentas desse cara e gosto de como elas funcionam :-)

Confira Classdesc http://classdesc.sf.net.Ele fornece reflexão na forma de "descritores" de classe, funciona com qualquer compilador C++ padrão (sim, é conhecido por funcionar com Visual Studio e também com GCC) e não requer anotação de código-fonte (embora existam alguns pragmas para lidar com situações complicadas ).Ele está em desenvolvimento há mais de uma década e é usado em vários projetos em escala industrial.

Quando eu queria reflexão em C++ eu li Este artigo e melhorei o que vi lá.Desculpe, não pode.Eu não sou o dono do resultado... mas você certamente pode conseguir o que eu tinha e partir daí.

Atualmente estou pesquisando, quando tenho vontade, métodos para usar inherit_linearly para tornar a definição de tipos refletidos muito mais fácil.Na verdade, cheguei bastante longe, mas ainda tenho um longo caminho a percorrer.As mudanças no C++ 0x provavelmente serão de grande ajuda nesta área.

Parece que o C++ ainda não possui esse recurso.E C++11 reflexão adiada também ((

Pesquise algumas macros ou crie as suas próprias.Qt também pode ajudar na reflexão (se puder ser usado).

Tente olhar para este projeto http://www.garret.ru/cppreflection/docs/reflect.htmlsão adicionados reflexos ao C++.Ele adicionou metadados às classes que você pode usar.

mesmo que a reflexão não seja suportada imediatamente em c++, ela não é muito difícil de implementar.Encontrei este ótimo artigo:http://replicaisland.blogspot.co.il/2010/11/building-reflective-object-system-in-c.html

o artigo explica detalhadamente como você pode implementar um sistema de reflexão bastante simples e rudimentar.admito que não é a solução mais saudável e ainda há arestas a serem resolvidas, mas para minhas necessidades foi suficiente.

o resultado final - a reflexão pode valer a pena se for feita corretamente e é completamente viável em c++.

Gostaria de anunciar a existência do kit de ferramentas automáticas de introspecção/reflexão "IDK".Ele usa um meta-compilador como o do Qt e adiciona metainformações diretamente nos arquivos-objeto.Alega-se que é fácil de usar.Sem dependências externas.Ele ainda permite que você reflita automaticamente std::string e depois use-o em scripts.Por favor, olhe EU NÃO SEI

A reflexão em C++ é muito útil, nesses casos você precisa executar algum método para cada membro (por exemplo:serialização, hash, comparação).Eu vim com uma solução genérica, com sintaxe bem simples:

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

Onde ENUMERATE_MEMBERS é uma macro, descrita posteriormente (UPDATE):

Suponha que definimos a função de serialização para int e std::string assim:

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());
}

E temos uma função genérica perto da "macro secreta";)

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

Agora você pode escrever

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)

Portanto, tendo a macro ENUMERATE_MEMBERS na definição da estrutura, você pode construir serialização, comparação, hash e outras coisas sem tocar no tipo original, o único requisito é implementar o método "EnumerateWith" para cada tipo, que não é enumerável, por enumerador (como BinaryWriter) .Normalmente você terá que implementar de 10 a 20 tipos "simples" para suportar qualquer tipo em seu projeto.

Esta macro deve ter sobrecarga zero para estruturar a criação/destruição em tempo de execução, e o código de T.EnumerateWith() deve ser gerado sob demanda, o que pode ser alcançado tornando-a uma função inline de modelo, de modo que a única sobrecarga em toda a história é adicionar ENUMERATE_MEMBERS(m1,m2,m3...) a cada estrutura, enquanto a implementação de um método específico por tipo de membro é obrigatória em qualquer solução, portanto, não assumo isso como sobrecarga.

ATUALIZAR:Há uma implementação muito simples da macro ENUMERATE_MEMBERS (no entanto, poderia ser um pouco estendida para suportar herança de estrutura enumerável)

#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);
}

E você não precisa de nenhuma biblioteca de terceiros para essas 15 linhas de código;)

Com C++20, você tem declarações de expansão, que permite iterar em tipos de agregações:

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>();
}

Se você está procurando uma reflexão C++ relativamente simples - coletei macros/definições de várias fontes e comentei como elas funcionam.Você pode baixar arquivos de cabeçalho daqui:

https://github.com/tapika/TestCppReflect/blob/master/MacroHelpers.h

conjunto de definições, além de funcionalidades adicionais:

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

O aplicativo de exemplo também reside no repositório git, aqui:https://github.com/tapika/TestCppReflect/

Vou copiá-lo parcialmente aqui com explicação:

#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 usa nome de classe + nome de campo com offsetof - identificar em que local da memória determinado campo está localizado.Tentei usar a terminologia .NET na medida do possível, mas C++ e C# são diferentes, portanto não é 1 para 1.Todo o modelo de reflexão C++ reside em TypeInfo e FieldInfo Aulas.

Eu usei o analisador pugi xml para buscar o código de demonstração no xml e restaurá-lo do xml.

Portanto, a saída produzida pelo código de demonstração fica assim:

<?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>

Também é possível ativar qualquer suporte de classe/estrutura de terceiros por meio da classe TypeTraits e especificação parcial do modelo - para definir sua própria classe TypeTraitsT, de maneira semelhante a CString ou int - veja o código de exemplo em

https://github.com/tapika/TestCppReflect/blob/master/TypeTraits.h#L195

Esta solução é aplicável para Windows/Visual Studio.É possível portá-lo para outros sistemas operacionais/compiladores, mas ainda não fiz isso.(Pergunte-me se você realmente gosta de solução, talvez eu possa ajudá-lo)

Esta solução é aplicável para serialização única de uma classe com múltiplas subclasses.

No entanto, se você estiver procurando por um mecanismo para serializar partes da classe ou até mesmo para controlar quais funcionalidades as chamadas de reflexão produzem, você pode dar uma olhada na seguinte solução:

https://github.com/tapika/cppscriptcore/tree/master/SolutionProjectModel

Informações mais detalhadas podem ser encontradas no vídeo do youtube:

Reflexão do tipo de tempo de execução C++https://youtu.be/TN8tJijkeFE

Estou tentando explicar um pouco mais profundamente como a reflexão em c++ funcionará.

O código de exemplo será semelhante a este:

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;

Mas cada etapa aqui realmente resulta em chamadas de função usando propriedades C ++ com __declspec(property(get =, put ... ).

que recebe informações completas sobre tipos de dados C++, nomes de propriedades C++ e ponteiros de instância de classe, em forma de caminho, e com base nessas informações você pode gerar xml, json ou até mesmo serializá-lo pela internet.

Exemplos dessas funções de retorno de chamada virtual podem ser encontrados aqui:

https://github.com/tapika/cppscriptcore/blob/master/SolutionProjectModel/VCConfiguration.cpp

Veja funções ReflectCopy, e função virtual ::OnAfterSetProperty.

Mas como o tópico é realmente avançado, recomendo verificar primeiro o vídeo.

Se você tiver alguma ideia de melhoria, não hesite em entrar em contato comigo.

O projeto Root Reflex tem suporte para isso.

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

Se você declarar um ponteiro para uma função como esta:

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

Você pode atribuir um lugar na memória para essa função assim (requer libdl e 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);
}

Para carregar um símbolo local usando indiretamente, você pode usar dlopen no binário de chamada (argv[0]).

O único requisito para isso (além dlopen(), libdl, e dlfcn.h) é conhecer os argumentos e o tipo da função.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top