Como posso adicionar reflexão a um aplicativo C ++?


263

Eu gostaria de poder examinar uma classe C ++ por seu nome, conteúdo (por exemplo, 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 essas informações?


18
Muita sorte, você não pode fazer isso sem macros e outros pré-processos, porque os metadados necessários não existem, a menos que você os crie manualmente através de alguma mágica de pré-processamento de macro.
jalf

6
As informações que você pode recuperar do RTTI não são suficientes para fazer a maioria das coisas pelas quais você realmente deseja refletir. Você não pode iterar sobre as funções de membro de uma classe, por exemplo.
317 Joseph Garvin

Respostas:


259

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 mais limpo escrevê-lo no pré-processador, usaremos a 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 xvocê 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

Em seguida, definimos uma REFLECTABLEmacro para gerar os dados sobre cada campo (mais o próprio campo). Essa macro será chamada assim:

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

Portanto, usando o Boost.PP , iteramos sobre cada argumento e geramos os dados desta maneira:

// 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_nque é o número de campos refletíveis na classe. Em seguida, ele é especializado field_datapara cada campo. Também é amigo da reflectorturma, para que ele possa acessar os campos mesmo quando 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 sobre os campos, usamos o padrão de visitante. Criamos um intervalo de 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, no momento da verdade, reunimos tudo. Aqui está como podemos definir uma Personclasse que é refletível:

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

Aqui está uma print_fieldsfunção generalizada 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 de print_fieldscom a Personclasse refletível :

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.


106
Parabéns por mostrar como implementar a reflexão, em vez de dizer que não pode ser feito. São respostas como essa que tornam o SO um ótimo recurso.
Fearless_fool

4
Observe que, se você tentar compilar isso no Visual Studio, receberá um erro porque o VS não manipula a expansão de macro variada corretamente. Para VS, tente adicionar: #define DETAIL_TYPEOF_INT2(tuple) DETAIL_TYPEOF_HEAD tuplee #define DETAIL_TYPEOF_INT(...) DETAIL_TYPEOF_INT2((__VA_ARGS__)) e alterando a definição de typeof (x):#define TYPEOF(x) DETAIL_TYPEOF_INT(DETAIL_TYPEOF_PROBE x,)
Phenglei Kai

Estou recebendo o erro 'BOOST_PP_IIF_0' não nomeia um tipo. Você pode por favor ajudar.
Ankit Zalani 04/04

3
Veja minha própria resposta - stackoverflow.com/a/28399807/2338477 Eu extraí e reembalei todas as definições, e a biblioteca de reforço não é necessária. Como código de demonstração, estou fornecendo serialização para xml e restauro de xml.
TarmoPikaro

106

Existem dois tipos de reflectionnatação ao redor.

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

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

    Esse tipo de coisa é possível com o C ++ usando template-tricks. Use boost::type_traitspara 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 SFINAE simples .

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

  • Um Meta Compiler como o Qt Meta Object Compiler que traduz seu código adicionando meta informações adicionais.
  • Uma estrutura que consiste em macros que permitem adicionar as meta-informações necessárias. Você precisaria informar à estrutura todos os métodos, nomes de classes, classes base e tudo o que precisa.

C ++ é feito com velocidade em mente. Se você deseja uma inspeção de alto nível, como C # ou Java, receio dizer que não há maneira sem esforço.


122
O C ++ é feito tendo em mente a velocidade, mas a filosofia não é "o mais rápido possível", é "você não paga por isso se não o usa". Eu acredito que é possível para uma linguagem implementar a introspecção de uma maneira que se encaixa nessa filosofia, mas o C ++ não possui.
31830 Joseph Garvin

8
@ Joseph: Como isso deve ser feito? Exigiria que todos os metadados fossem armazenados. O que significa que você deve pagar por isso, mesmo que não o use. (A menos que você poderia marcar tipos individuais como "reflexão apoio", mas então estamos quase até onde podemos muito bem usar o artifício macro existente.
jalf

25
@ jalf: Apenas os metadados que podem ser necessários. Se considerarmos apenas a reflexão em tempo de compilação, isso é trivial. Por exemplo, uma função em tempo de compilação members<T>que retorna uma lista de todos os membros de T. Se quiséssemos ter reflexão em tempo de execução (isto é, RTTI misturado com reflexão), o compilador ainda saberia todos os tipos de base refletidos. Provavelmente members<T>(T&), nunca seria instanciado para T = std :: string, portanto, o RTTI para std :: string ou suas classes derivadas não precisam ser incluídas.
MSalters

9
A biblioteca reflex (mencionado abaixo) acrescenta reflexão para C ++, sem abrandar o código existente em: root.cern.ch/drupal/content/reflex
Joseph Lisee

6
@ Joe: Reflexão nunca diminui o código existente. Isso apenas aumenta o material entregue (já que você precisa fornecer um banco de dados de informações do tipo ...).
mmmmmmmm

56

E eu adoraria um pônei, mas os pôneis não são livres. :-p

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


1
Eu segundo Brad. Os modelos C ++ podem ser bastante poderosos, e há uma vasta experiência em torno de vários comportamentos do tipo 'reflexão', como aumentar a biblioteca 'any', traços de tipo, C ++ RTTI etc. que podem resolver muitos dos problemas pelos quais a reflexão é resolvida. Então Nick, qual é o seu objetivo aqui?
Aaron

7
Voto a favor da observação do pôneis! Eu votaria duas vezes, pois sua resposta também merece, mas, infelizmente, recebo apenas um, para que os pôneis ganhem. :-)
Franci Penov

6
Eu realmente não entendo por que essa é uma resposta inteligente. Eu já disse que gostaria de referências a bibliotecas etc para implementar isso. A reflexão / introspecção é para vários sistemas para permitir o acesso de script, serialização etc.
Nick

3
@ Nick: Ele já respondeu isso. Isso não pode ser feito, os dados não existem e, portanto, nenhuma biblioteca pode implementá-los para você.
jalf

@ jalf Ainda é estranho para mim ler as pessoas no mundo da programação dizendo que pensam como 'não é possível' e não 'não sei como'. Claro que os metadados não existem, mas podem ser inseridos com macros
Freddx L.

38

As informações existem - mas não no formato que você precisa e somente se você exportar suas classes. Isso funciona no Windows, não conheço outras plataformas. Usando os especificadores da classe de armazenamento como em, por exemplo:

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

Isso faz com que o compilador crie os dados de definição de classe na DLL / Exe. Mas não está em um formato que você possa usar prontamente 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. Permite que as funções sejam chamadas da seguinte maneira:

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, chamando uma função dessa maneira, você está contornando coisas como const-safety e assim por diante, para que esses aspectos sejam implementados como verificações de tempo de execução.

Tenho certeza de que a sintaxe pode ser aprimorada, e só funciona no Win32 e Win64 até agora. Nós achamos 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 é necessário derivar de uma classe base específica. Se houver demanda suficiente, talvez possamos colocá-lo em forma para lançamento.


1
Eu acho que você quer dizer __declspec(dllexport)e você pode recuperar as informações de um arquivo .map se você habilitar a criação de tais durante a compilação.
Orwellophile

18

A reflexão não é suportada pelo C ++ imediatamente. Isso é triste, porque faz do teste defensivo uma dor.

Existem várias abordagens para refletir:

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

O primeiro link parece o mais promissor (usa mod's para clang), o segundo discute várias 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

Agora existe um grupo de trabalho para reflexão em C ++. Veja as notícias do C ++ 14 @ CERN:

Editar 13/08/17:

Desde o post original, houve vários avanços em potencial na reflexão. A seguir, são fornecidos mais detalhes e uma discussão sobre as várias 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 em uma abordagem padronizada de reflexões em C ++ em um futuro próximo, a menos que haja muito mais interesse da comunidade em apoiar a reflexão em C ++.

A seguir, detalha o status atual com base nos comentários da última reunião de padrões de C ++:

Editar 13/12/2017

A reflexão parece estar se movendo em direção a C ++ 20 ou mais, provavelmente um TSR. O movimento é contudo lento.

Editar 15/09/2018

Um projeto de TS foi enviado aos órgãos nacionais para votação.

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

Editar 11/07/2019

A reflexão TS é um recurso completo e está fora para comentários e votação durante o verão (2019).

A abordagem de programação de meta-modelos deve ser substituída por uma abordagem mais simples de código de tempo de compilação (não refletida no TS).

Editar 10/02/2020

Há uma solicitação para oferecer suporte à reflexão TS no Visual Studio aqui:

Fale sobre o TS pelo autor David Sankel:

Editar 17 de março de 2020

Progresso na reflexão está sendo feito. Um relatório do 'Relatório de viagem do comitê ISO C ++ 2020-02 de Praga' pode ser encontrado aqui:

Detalhes sobre o que está sendo considerado para o C ++ 23 podem ser encontrados aqui (inclui uma breve seção sobre Reflexão):

Editar 4 de junho de 2020

Uma nova estrutura foi lançada por Jeff Preshing chamada 'Plywood' que contém um mecanismo para reflexão em tempo de execução. Mais detalhes podem ser encontrados aqui:

As ferramentas e a abordagem parecem ser as mais polidas e mais fáceis de usar até agora.


1
O link cern está quebrado.
Mostowski Collapse

Os links de referência devem ser corrigidos agora. Eles tendem a quebrar com bastante frequência, o que é uma dor.
Damian Dixon

Esta resposta considera apenas a reflexão em tempo de compilação?
einpoklum

@einpoklum as únicas soluções atuais para reflexão são o tempo de compilação, geralmente com código de meta-modelo ou macro. O último rascunho de TS parece que deve funcionar em tempo de execução, mas você precisará criar todas as bibliotecas com o compilador correto para que os metadados necessários sejam armazenados.
Damian Dixon

@ DamianDixon: Isso não é verdade. Existem várias bibliotecas de reflexão em tempo de execução. Agora, eles são bastante desajeitados e optam por participar ou requerem nodifications do compilador, mas ainda existem. Se, como eu entendi o seu comentário, você se referiu apenas à reflexão em tempo de compilação, edite sua resposta para torná-la mais clara.
einpoklum

15

Você precisa analisar o que está tentando fazer e se o RTTI atenderá aos seus requisitos. Eu implementei minha própria pseudo-reflexão para alguns propósitos muito específicos. Por exemplo, uma vez eu queria poder configurar com flexibilidade o que uma simulação produziria. É necessário adicionar algum código padrão às classes que seriam produzidas:

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

Por meio de alguma mágica envolvendo modelos boost, isso é traduzido em uma série de chamadas de método em tempo de execução (quando o arquivo de configuração é lido), por isso é bastante eficiente. Eu não recomendaria fazer isso, a menos que você realmente precise, mas, quando precisar, poderá fazer algumas coisas bem legais.


preciso amar essas funções que sempre retornam verdadeiras;) Presumo que isso esteja imune a problemas estáticos de pedidos de inicialização?
paulm

14

Eu recomendaria usar o Qt .

Existe uma licença de código-fonte aberto e também uma licença comercial.


1
Eu olhei para isso, mas ele usa macros e o código fonte precisa ser analisado para gerar o código de metadados. Eu gostaria de evitar essa etapa extra. Eu preferiria usar uma biblioteca C ++ ou macros simples. Obrigado pela ideia.
Nick

10
QT, ou outra biblioteca de implementar uma abordagem semelhante é o melhor que você vai conseguir
jalf

5
Pague em tempo de compilação ou em tempo de execução - de qualquer maneira que você estiver pagando!
Martin Beckett

13

O que você está tentando fazer com a reflexão?
Você pode usar as características do tipo Boost e o 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.


13

EDIT : CAMP não é mais mantido; dois garfos estão disponíveis:

  • Um também é chamado de CAMP e é baseado na mesma API.
  • Ponder é uma reescrita parcial e deve ser preferida, pois não requer Boost; está usando C ++ 11.

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

A atual biblioteca Tegesoft usa o Boost, mas também há um fork usando o C ++ 11 que não requer mais o Boost .


11

Fiz algo parecido com o que você procura uma vez 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 na manutenção pode não valer a pena. Meu sistema foi usado para manter as classes de interface do usuário completamente separadas da lógica de negócios por meio de delegação semelhante ao conceito de Objective-C de passagem e encaminhamento de mensagens. A maneira de fazer isso é criar alguma classe base capaz de mapear símbolos (usei um pool de strings, mas você poderia fazê-lo com enumerações, se preferir velocidade e manipulação de erros em tempo de compilação, em vez de flexibilidade total) para funcionar como ponteiros (na verdade não ponteiros de função pura, mas algo semelhante ao que o Boost possui com o Boost.Function - ao qual eu não tinha acesso no momento). Você pode fazer o mesmo com suas variáveis ​​de membro, desde que tenha alguma classe base comum capaz de representar qualquer valor. Todo o sistema era uma imitação descarada da codificação e delegação de valores-chave, com alguns efeitos colaterais que talvez valessem a quantidade de tempo necessária para que todas as classes que usassem o sistema combinassem todos os seus métodos e membros com chamadas legais : 1) Qualquer classe poderia chamar qualquer método em qualquer outra classe sem precisar incluir cabeçalhos ou escrever classes base falsas, para que a interface pudesse ser predefinida para o compilador; e 2) Os getters e setters das variáveis ​​membro eram fáceis de tornar seguros para threads, pois a alteração ou o acesso a seus valores sempre era feita através de 2 métodos na classe base de todos os objetos. Todo o sistema era uma imitação descarada da codificação e delegação de valores-chave, com alguns efeitos colaterais que talvez valessem a quantidade de tempo necessária para que todas as classes que usassem o sistema combinassem todos os seus métodos e membros com chamadas legais : 1) Qualquer classe poderia chamar qualquer método em qualquer outra classe sem precisar incluir cabeçalhos ou escrever classes base falsas, para que a interface pudesse ser predefinida para o compilador; e 2) Os getters e setters das variáveis ​​membro eram fáceis de tornar seguros para threads, pois a alteração ou o acesso a seus valores sempre era feita através de 2 métodos na classe base de todos os objetos. Todo o sistema era uma imitação descarada da codificação e delegação de valores-chave, com alguns efeitos colaterais que talvez valessem a quantidade de tempo necessária para que todas as classes que usassem o sistema combinassem todos os seus métodos e membros com chamadas legais : 1) Qualquer classe poderia chamar qualquer método em qualquer outra classe sem precisar incluir cabeçalhos ou escrever classes base falsas, para que a interface pudesse ser predefinida para o compilador; e 2) Os getters e setters das variáveis ​​membro eram fáceis de tornar seguros para threads, pois a alteração ou o acesso a seus valores sempre era feita através de 2 métodos na classe base de todos os objetos. 1) Qualquer classe poderia chamar qualquer método em qualquer outra classe sem precisar incluir cabeçalhos ou escrever classes base falsas, para que a interface pudesse ser predefinida para o compilador; e 2) Os getters e setters das variáveis-membro eram fáceis de tornar seguros para threads, pois a alteração ou acesso a seus valores sempre era feita através de 2 métodos na classe base de todos os objetos. 1) Qualquer classe poderia chamar qualquer método em qualquer outra classe sem precisar incluir cabeçalhos ou escrever classes base falsas, para que a interface pudesse ser predefinida para o compilador; e 2) Os getters e setters das variáveis ​​membro eram fáceis de tornar seguros para threads, pois a alteração ou o acesso a seus valores sempre era feita através de 2 métodos na classe base de todos os objetos.

Também levou à possibilidade de fazer coisas realmente estranhas que, de outra forma, não são fáceis em C ++. Por exemplo, eu poderia criar um objeto Array que continha itens arbitrários de qualquer tipo, inclusive ele próprio, e criar novas matrizes dinamicamente passando uma mensagem para todos os itens da matriz e coletando os valores de retorno (semelhante ao mapa no Lisp). Outra foi a implementação da observação de valores-chave, na qual eu consegui configurar a interface do usuário 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 o 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 na forma de string não menos.

Desvantagens do sistema que podem desencorajar você de se incomodar: adicionar todas as mensagens e valores-chave é extremamente entediante; é mais lento do que sem qualquer reflexão; você vai crescer a odiar ver boost::static_pointer_caste boost::dynamic_pointer_casttoda a sua base de código com uma paixão violenta; as limitações do sistema fortemente tipado ainda existem, você realmente as esconde um pouco, para que não seja tão óbvio. Erros de digitação em suas cordas também não são divertidos ou fáceis de descobrir surpresa.

Quanto à implementação de algo assim: basta usar ponteiros compartilhados e fracos para alguma base comum (a minha era muito imaginativamente chamada de "Objeto") e derivar para todos os tipos que você deseja usar. Eu recomendo instalar o Boost.Function em vez de fazê-lo da maneira que eu fiz, com algumas porcarias personalizadas e uma tonelada de macros feias para quebrar as chamadas do ponteiro de função. Como tudo é mapeado, a inspeção de objetos é apenas uma questão de iterar por 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 um modelo.


Ei, @ Michael; você ainda tem o código fonte para isso ou se livrou dele? Eu gostaria de dar uma olhada, se você não se importa.
RandomDSdevel

Opa, soletrou seu nome errado! Não é de admirar que eu nunca recebi uma resposta ...
RandomDSdevel

10

Há 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.


8

As duas soluções de reflexão que conheço nos meus dias em C ++ são:

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

2) Outra opção, se você tiver acesso aos objetos do compilador, é usar o DIA SDK . Se bem me lembro, isso permite abrir pdbs, que deve conter metadados para seus tipos de C ++. Pode ser o suficiente para fazer o que você precisa. Esta página mostra como você pode obter todos os tipos de 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.


Isso é astuto e um hack gigante, com a coisa do DIA SDK que você sugeriu lá.
Sqeaky

7

EDIT: link quebrado atualizado a partir de 7 de fevereiro de 2017.

Eu acho que ninguém mencionou isso:

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

Reflex do CERN . Isso parece funcionar muito bem.


@ j4nbur53 O link está quebrado porque parece que eles atingiram um marco: root.cern.ch
Germán Diago

Será que você quis dizer esse link root.cern.ch/root/doc/ROOTUsersGuideHTML/ch07.html Chapter Reflex?
Mostowski Collapse

Experimente este root.cern.ch/how/how-use-reflex . O Reflex funciona como um gerador que analisa seus arquivos de cabeçalho e gera código / biblioteca de introspecção c ++, que você pode vincular e usar uma API simples.
Adam Ryczkowski

6

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

Cabe a 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 BOOST_FUSION_ADAPT_STRUCTaquela poderia gerar os métodos necessários para obter o comportamento em tempo de execução.


2
por minghua (que originalmente editou o post): Eu procurei nesta solução BOOST_FUSION_ADAPT_STRUCT e, eventualmente, criei um exemplo. Veja esta pergunta SO mais recente - C ++ itera no campo struct aninhado com boost fusion adap_struct .
Matthieu

Ótimo, Matthieu! Acabei de perceber que vimos suas dicas aqui e ali ao longo do ano passado. Não percebeu que eles estão relacionados até agora. Aqueles foram muito inspiradores.
Minghua 6/09/12

6

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


4

Ponder é uma biblioteca de reflexão em C ++, em resposta a esta pergunta. Eu considerei as opções e decidi fazer as minhas próprias, pois não consegui encontrar uma que marque todas as minhas caixas.

Embora haja ótimas respostas para essa pergunta, não quero usar toneladas de macros ou confiar no Boost. O Boost é uma ótima biblioteca, mas existem muitos projetos C ++ 0x personalizados, mais simples e com 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. É o fork do CAMP, usando C ++ 11, que não requer mais o Boost .


4

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; porque a maioria das pessoas não usa / deseja reflexão, o compilador C ++ evita o custo por não gravar nada .

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 tiver um idioma com reflexão, obtenha uma ferramenta que possa extrair as informações desejadas em tempo de compilação.

Nosso DMS Software Reengineering Toolkit é uma tecnologia generalizada de compilador, parametrizada por definições explícitas de linguagem. Possui definições de idioma para C, C ++, Java, COBOL, PHP, ...

Para versões C, C ++, Java e COBOL, fornece acesso completo a árvores de análise e informações da tabela de símbolos. As informações da tabela de símbolos incluem o tipo de dados que você provavelmente deseja 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 maneira arbitrária.


3

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

Também me interessei em refletir sobre o meu projeto e encontrei esta biblioteca, ainda não tentei, mas tentei outras ferramentas desse cara e gosto de como elas funcionam :-)


3

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


1
Bem-vindo ao Stack Overflow. Embora esta resposta é sobre o tema, é importante salientar que você é o autor deste software, para deixar claro que não é uma recomendação imparcial :-)
Matthew Strawbridge

2

Quando eu queria reflexão em C ++, li este artigo e aprimorei o que vi lá. Desculpe, não pode ter. Eu não possuo o resultado ... mas você certamente pode obter o que eu tinha e partir daí.

Atualmente, estou pesquisando, quando me apetece, métodos para usar o herdit_linearly para facilitar a definição de tipos refletíveis. Na verdade, cheguei bastante longe, mas ainda tenho um longo caminho a percorrer. As alterações no C ++ 0x provavelmente ajudarão bastante nessa área.


2

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

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


2

mesmo que o reflexo não seja suportado imediatamente no c ++, 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 em detalhes como você pode implementar um sistema de reflexão bastante simples e rudimentar. concedida não é a solução mais saudável, e ainda há arestas a serem resolvidas, mas para as minhas necessidades foi suficiente.

a linha inferior - a reflexão pode render se feita corretamente, e é completamente viável em c ++.


2

Gostaria de anunciar a existência do kit de ferramentas de introspecção / reflexão automático "IDK". Ele usa um meta-compilador como o Qt e adiciona informações meta diretamente nos arquivos de objeto. Alega-se ser fácil de usar. Sem dependências externas. Ele ainda permite refletir automaticamente std :: string e usá-lo em scripts. Por favor, olhe para o IDK


2

Se você está procurando uma reflexão C ++ relativamente simples - eu coletei de várias fontes macro / define e comentei como elas funcionam. Você pode baixar os arquivos de cabeçalho aqui:

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

conjunto de definições, além de funcionalidade:

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

}

REFLECTABLEdefine usa o nome da classe + o nome do campo com offsetof- para identificar em qual local da memória um campo específico está localizado. Eu tentei pegar a terminologia .NET o máximo possível, mas C ++ e C # são diferentes, portanto, não é 1 para 1. Todo o modelo de reflexão C ++ reside em TypeInfoe FieldInfoclasses.

Eu usei o pugi xml parser para buscar o código de demonstração no xml e restaurá-lo novamente a partir 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 de modelo - para definir sua própria classe TypeTraitsT, da mesma maneira que CString ou int - consulte o código de exemplo em

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

Esta solução é aplicável ao Windows / Visual studio. É possível portá-lo para outros SO / compiladores, mas não o fez. (Pergunte-me se você realmente gosta de solução, posso ajudá-lo)

Esta solução é aplicável para serialização instantânea de uma classe com várias subclasses.

Se, no entanto, você estiver procurando por um mecanismo para serializar partes da classe ou mesmo para controlar o que as chamadas de reflexão de funcionalidade produzem, dê 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 sobre como a reflexão em c ++ funcionará.

O código de exemplo será parecido com, por exemplo:

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 chamada de função Using C ++ properties with __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, na forma de caminho, e com base nessas informações, você pode gerar xml, json ou até serializar aquele pela Internet.

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

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

Veja funções ReflectCopye função virtual ::OnAfterSetProperty.

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

Se você tiver algumas idéias de melhoria, não hesite em entrar em contato comigo.


1

A reflexão em C ++ é muito útil, nos casos em que você precisa executar algum método para cada membro (por exemplo: serialização, hash, comparação). Eu vim com solução genérica, com sintaxe muito 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 mais adiante (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 nós 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, com a macro ENUMERATE_MEMBERS na definição da estrutura, é possível criar serialização, comparação, hash e outros itens 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ê precisará implementar de 10 a 20 tipos "simples" para oferecer suporte a qualquer tipo no seu projeto.

Essa macro deve ter zero de sobrecarga 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 com a função de linha de modelo, portanto, a única sobrecarga em toda a história é adicionar ENUMERATE_MEMBERS (m1, m2, m3 ...) a cada estrutura, enquanto a implementação de métodos específicos por tipo de membro é uma obrigação em qualquer solução, por isso não assumo isso como sobrecarga.

UPDATE: Existe uma implementação muito simples da macro ENUMERATE_MEMBERS (no entanto, pode ser um pouco estendido para suportar a herança de estruturas enumeráveis)

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


1

Você pode obter recursos interessantes de reflexão estática para estruturas com o BOOST_HANA_DEFINE_STRUCT da biblioteca Boost :: Hana.
Hana é bastante versátil, não apenas para o caso de uso que você tem em mente, mas também para muita metaprogramação de modelos.


1

A reflexão de acesso aleatório biblioteca permite uma reflexão bastante fácil e intuitiva - todas as informações de campo / tipo são projetadas para estarem disponíveis em matrizes ou parecerem com acesso a matrizes. Ele foi escrito para C ++ 17 e funciona com Visual Studios, g ++ e Clang. A biblioteca é apenas de cabeçalho, o que significa que você só precisa copiar "Reflect.h" no seu projeto para usá-lo.

Estruturas ou classes refletidas precisam da macro REFLECT, onde você fornece o nome da classe que está refletindo e os nomes dos campos.

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

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

Isso é tudo o que existe, nenhum código adicional é necessário para configurar a reflexão. Opcionalmente, você pode fornecer superclasses (entre parênteses do primeiro argumento) e anotações de campo (entre parênteses anteriores ao campo que deseja anotar) para poder atravessar superclasses ou adicionar informações adicionais em tempo de compilação a um campo (como Json: :Ignorar).

Dar laços nos campos pode ser tão simples quanto ...

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

Você pode percorrer uma instância do objeto para acessar valores de campo (que você pode ler ou modificar) e informações de tipo de campo ...

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

Uma biblioteca JSON é construída sobre RandomAccessReflection, que identifica automaticamente representações de saída JSON apropriadas para leitura ou gravação e pode percorrer recursivamente qualquer campo refletido, bem como matrizes e contêineres 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);
}

O acima pode ser executado assim ...

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

Veja também...


0

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 a essa função como esta (requer libdle 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 dlopeno binário chamador ( argv[0]).

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

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.