Come posso aggiungere la riflessione a un'applicazione C ++?


271

Mi piacerebbe essere in grado di analizzare una classe C ++ per il suo nome, i contenuti (cioè i membri ei loro tipi) ecc. Sto parlando di C ++ nativo qui, C ++ non gestito, che ha la riflessione. Mi rendo conto che il C ++ fornisce alcune informazioni limitate usando RTTI. Quali librerie aggiuntive (o altre tecniche) potrebbero fornire queste informazioni?


18
Per fortuna, non puoi farlo senza macro e altre preelaborazioni, perché i metadati richiesti non esistono a meno che non li crei manualmente attraverso qualche magia di preelaborazione macro.
jalf

6
Le informazioni che puoi ottenere da RTTI non sono sufficienti per fare la maggior parte delle cose per le quali vorresti effettivamente riflettere. Ad esempio, non è possibile iterare sulle funzioni membro di una classe.
Joseph Garvin,

Risposte:


272

Quello che devi fare è che il preprocessore generi i dati di riflessione sui campi. Questi dati possono essere memorizzati come classi annidate.

In primo luogo, per rendere più facile e più pulito scriverlo nel preprocessore useremo l'espressione digitata. Un'espressione digitata è solo un'espressione che mette il tipo tra parentesi. Quindi invece di scrivere int xscriverai (int) x. Ecco alcune macro utili per aiutare con le espressioni digitate:

#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

Successivamente, definiamo una REFLECTABLEmacro per generare i dati su ogni campo (più il campo stesso). Questa macro verrà chiamata in questo modo:

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

Quindi, usando Boost.PP iteriamo su ogni argomento e generiamo i dati in questo modo:

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

Ciò che fa è generare una costante fields_nche è il numero di campi riflettibili nella classe. Quindi si specializza field_dataper ogni campo. Inoltre è amico della reflectorclasse, in questo modo può accedere ai campi anche quando sono privati:

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

Ora per iterare sui campi usiamo il pattern visitatore. Creiamo un intervallo MPL da 0 al numero di campi e accediamo ai dati del campo a quell'indice. Quindi passa i dati del campo al visitatore fornito dall'utente:

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

Adesso per il momento della verità mettiamo tutto insieme. Ecco come possiamo definire una Personclasse riflettibile:

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

Ecco una print_fieldsfunzione generalizzata che utilizza i dati di riflessione per iterare sui campi:

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

Un esempio di utilizzo di print_fieldscon la Personclasse riflettente :

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

Quali uscite:

name=Tom
age=82

E voilà, abbiamo appena implementato la reflection in C ++, in meno di 100 righe di codice.


114
Complimenti per aver mostrato come implementare la riflessione, piuttosto che dire che non può essere fatto. Sono risposte come questa che rendono SO una grande risorsa.
fearless_fool

5
Nota che se provi a compilarlo in Visual Studio riceverai un errore perché VS non gestisce correttamente l'espansione della macro variadica. Per VS, prova ad aggiungere: #define DETAIL_TYPEOF_INT2(tuple) DETAIL_TYPEOF_HEAD tuplee #define DETAIL_TYPEOF_INT(...) DETAIL_TYPEOF_INT2((__VA_ARGS__)) e cambiando la definizione di TypeOf (x) a:#define TYPEOF(x) DETAIL_TYPEOF_INT(DETAIL_TYPEOF_PROBE x,)
Phenglei Kai

Ricevo l'errore "BOOST_PP_IIF_0" non indica un tipo. Puoi per favore aiutare.
Ankit Zalani

3
Vedi la mia risposta: stackoverflow.com/a/28399807/2338477 Ho estratto e reimballato tutte le definizioni e la libreria boost non è necessaria. Come codice demo sto fornendo la serializzazione a xml e il ripristino da xml.
TarmoPikaro

106

Ci sono due tipi di reflectionnuoto in giro.

  1. Ispezione iterando sui membri di un tipo, enumerandone i metodi e così via.

    Questo non è possibile con C ++.
  2. L'ispezione verificando se un tipo di classe (classe, struttura, unione) ha un metodo o un tipo annidato, è derivato da un altro tipo particolare.

    Questo genere di cose è possibile con C ++ usando template-tricks. Da utilizzare boost::type_traitsper molte cose (come controllare se un tipo è integrale). Per verificare l'esistenza di una funzione membro, utilizzare È possibile scrivere un modello per verificare l'esistenza di una funzione? . Per verificare se esiste un certo tipo annidato, usa SFINAE semplice .

Se stai piuttosto cercando modi per ottenere 1), come guardare quanti metodi ha una classe, o come ottenere la rappresentazione di stringa di un id di classe, allora temo che non ci sia un modo C ++ standard per farlo. Devi usare entrambi

  • Un Meta Compiler come il Qt Meta Object Compiler che traduce il tuo codice aggiungendo meta informazioni aggiuntive.
  • Un Framework costituito da macro che consentono di aggiungere le meta-informazioni richieste. Dovresti dire al framework tutti i metodi, i nomi delle classi, le classi base e tutto ciò di cui ha bisogno.

Il C ++ è stato creato pensando alla velocità. Se vuoi un'ispezione di alto livello, come C # o Java, allora temo di doverti dire che non c'è modo senza un po 'di sforzo.


129
Il C ++ è stato creato pensando alla velocità, ma la filosofia non è "il più veloce possibile", bensì "non paghi se non lo usi". Credo che sia possibile per un linguaggio implementare l'introspezione in un modo che si adatti a quella filosofia, il C ++ semplicemente manca.
Joseph Garvin,

8
@ Joseph: Come dovrebbe essere fatto? Richiederebbe l'archiviazione di tutti i metadati. Il che significa che devi pagare per questo, anche se non lo usi. (A meno che tu non possa contrassegnare i singoli tipi come "riflessione di supporto", ma allora siamo quasi giunti al punto in cui potremmo anche usare il trucco macro esistente.
jalf

26
@jalf: solo i metadati che potrebbero essere necessari. Se consideriamo solo la riflessione in fase di compilazione, questo è banale. Ad esempio, una funzione in fase di compilazione members<T>che restituisce un elenco di tutti i membri di T. Se volessimo avere una riflessione runtime (cioè RTTI mescolata con riflessione), il compilatore dovrebbe comunque conoscere tutti i tipi di base riflessi. È molto probabile members<T>(T&)che non venga mai istanziato per T = std :: string, quindi non è necessario includere l'RTTI per std :: string o le sue classi derivate.
MSalters

9
La libreria reflex (menzionata di seguito) aggiunge la riflessione a C ++ senza rallentare il codice esistente su: root.cern.ch/drupal/content/reflex
Joseph Lisee

6
@ Joe: Reflection non rallenta mai il codice esistente. Rende solo più grandi le cose consegnate (dal momento che devi fornire un database di informazioni sul tipo ...).
mmmmmmmm

57

E mi piacerebbe un pony, ma i pony non sono gratuiti. :-p

http://en.wikibooks.org/wiki/C%2B%2B_Programming/RTTI è ciò che otterrai. Riflessione come stai pensando - metadati completamente descrittivi disponibili in fase di esecuzione - semplicemente non esistono per C ++ per impostazione predefinita.


1
Secondo Brad. I modelli C ++ possono essere piuttosto potenti e c'è una vasta esperienza su vari comportamenti di tipo "riflessione", come il potenziamento di libreria "qualsiasi", tratti di tipo, RTTI C ++ ecc. Che possono risolvere molti dei problemi per i quali viene risolta la riflessione. Allora Nick, qual è il tuo obiettivo qui?
Aaron

7
Upvote per l'osservazione dei pony! Voterei due volte, poiché anche la tua risposta lo merita, ma purtroppo ne ottengo solo uno, quindi i pony vincono. :-)
Franci Penov

7
Non capisco davvero perché questa sia una risposta intelligente. Ho già detto che vorrei fare riferimento a librerie ecc per implementarlo. La riflessione / introspezione è per vari sistemi per consentire l'accesso allo script, la serializzazione ecc.
Nick

4
@ Nick: ha già risposto. Non si può fare, i dati non esistono e quindi nessuna libreria è in grado di implementarli per te.
jalf

@jalf Ancora strano per me leggere le persone nel mondo della programmazione che dicono "non è possibile" e non "Non so come". Sicuramente i metadati non esistono ma possono essere inseriti con le macro
Freddx L.

40

Le informazioni esistono, ma non nel formato di cui hai bisogno e solo se esporti le tue classi. Funziona in Windows, non conosco altre piattaforme. Utilizzando gli identificatori della classe di archiviazione come, ad esempio:

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

Ciò fa sì che il compilatore crei i dati di definizione della classe nella DLL / Exe. Ma non è in un formato che puoi usare prontamente per la riflessione.

Nella mia azienda abbiamo creato una libreria che interpreta questi metadati e ti consente di riflettere una classe senza inserire macro extra ecc. Nella classe stessa. Consente di chiamare le funzioni come segue:

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

Questo fa efficacemente:

instance_ptr->Foo(1.331);

La funzione Invoke (this_pointer, ...) ha argomenti variabili. Ovviamente chiamando una funzione in questo modo stai aggirando cose come const-safety e così via, quindi questi aspetti sono implementati come controlli di runtime.

Sono sicuro che la sintassi potrebbe essere migliorata e finora funziona solo su Win32 e Win64. Lo abbiamo trovato davvero utile per avere interfacce GUI automatiche per le classi, creare proprietà in C ++, streaming da e verso XML e così via, e non c'è bisogno di derivare da una classe base specifica. Se c'è abbastanza domanda, forse potremmo metterlo in forma per il rilascio.


1
Penso che tu intenda __declspec(dllexport)e puoi recuperare le informazioni da un file .map se abiliti la creazione di tale durante la compilazione.
Orwellophile

23

La riflessione non è supportata da C ++ out of the box. Questo è triste perché rende i test difensivi un dolore.

Esistono diversi approcci per fare riflessione:

  1. usa le informazioni di debug (non portabile).
  2. Cospargi il tuo codice con macro / modelli o qualche altro approccio sorgente (sembra brutto)
  3. Modifica un compilatore come clang / gcc per produrre un database.
  4. Usa l'approccio Qt moc
  5. Boost Reflect
  6. Riflessione precisa e piatta

Il primo collegamento sembra il più promettente (usa le mod per suonare), il secondo discute una serie di tecniche, il terzo è un approccio diverso usando gcc:

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

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

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

Ora c'è un gruppo di lavoro per la riflessione C ++. Guarda le notizie per C ++ 14 al CERN:

Modifica 13/08/17:

Dal post originale ci sono stati numerosi potenziali progressi nella riflessione. Quanto segue fornisce maggiori dettagli e una discussione sulle varie tecniche e lo stato:

  1. Riflessione statica in poche parole
  2. Riflessione statica
  3. Un design per la riflessione statica

Tuttavia, non sembra promettente su un approccio di riflessione standardizzato in C ++ nel prossimo futuro a meno che non ci sia molto più interesse da parte della comunità nel supporto alla riflessione in C ++.

Di seguito viene descritto in dettaglio lo stato corrente in base al feedback dell'ultima riunione degli standard C ++:

Modifica 13/12/2017

La riflessione sembra muoversi verso C ++ 20 o più probabilmente un TSR. Il movimento è comunque lento.

Modifica 15/09/2018

Un progetto di TS è stato inviato agli organi nazionali per il ballottaggio.

Il testo può essere trovato qui: https://github.com/cplusplus/reflection-ts

Modifica 11/07/2019

La riflessione TS è completa ed è disponibile per commenti e votazioni durante l'estate (2019).

L'approccio di programmazione meta-template deve essere sostituito con un approccio più semplice per la compilazione del timecode (non riflesso nel TS).

Modifica 10/02/2020

C'è una richiesta per supportare la riflessione TS in Visual Studio qui:

Discorso sulla TS dell'autore David Sankel:

Modifica 17 marzo 2020

Si stanno compiendo progressi nella riflessione. Un rapporto dal '2020-02 Prague ISO C ++ Committee Trip Report' può essere trovato qui:

I dettagli su ciò che viene considerato per C ++ 23 possono essere trovati qui (include una breve sezione su Reflection):

Modifica 4 giugno 2020

Un nuovo framework è stato rilasciato da Jeff Preshing chiamato "Plywood" che contiene un meccanismo per la riflessione in fase di esecuzione. Maggiori dettagli possono essere trovati qui:

Gli strumenti e l'approccio sembrano essere i più raffinati e facili da usare finora.

Modifica 12 luglio 2020

Forcella di riflessione sperimentale Clang: https://github.com/lock3/meta/wiki

Interessante libreria di riflessione che utilizza la libreria di strumenti clang per estrarre informazioni per una semplice riflessione senza bisogno di aggiungere macro: https://github.com/chakaz/reflang


1
Il collegamento al cern è interrotto.
Mostowski Crollo

i collegamenti cern dovrebbero essere corretti ora. Tendono a rompersi abbastanza frequentemente, il che è un dolore.
Damian Dixon

Questa risposta riguarda solo la riflessione in fase di compilazione?
einpoklum

@einpoklum le uniche soluzioni attuali per la riflessione sono il tempo di compilazione, di solito con codice meta-modello o macro. L'ultima bozza di TS sembra che dovrebbe funzionare per il runtime, ma dovrai aver compilato tutte le librerie con il compilatore corretto per memorizzare i metadati necessari.
Damian Dixon

@DamianDixon: non è vero. Esistono diverse librerie di riflessione in fase di esecuzione. Ora, è vero, sono piuttosto goffi e sono o opt-in o richiedono modifiche al compilatore, ma esistono ancora. Se, da quanto ho capito il tuo commento, hai fatto riferimento solo alla riflessione in fase di compilazione, modifica la tua risposta per renderla più chiara.
einpoklum

15

Devi guardare a cosa stai cercando di fare e se RTTI soddisferà le tue esigenze. Ho implementato la mia pseudo-riflessione per scopi molto specifici. Ad esempio, una volta volevo essere in grado di configurare in modo flessibile ciò che avrebbe prodotto una simulazione. Richiedeva l'aggiunta di codice boilerplate alle classi che sarebbero state emesse:

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

La prima chiamata aggiunge questo oggetto al sistema di filtraggio, che chiama il BuildMap()metodo per capire quali metodi sono disponibili.

Quindi, nel file di configurazione, puoi fare qualcosa del genere:

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

Attraverso un po 'di magia dei modelli boost, questo viene tradotto in una serie di chiamate di metodo in fase di esecuzione (quando il file di configurazione viene letto), quindi è abbastanza efficiente. Non consiglierei di farlo a meno che non sia davvero necessario, ma, quando lo fai, puoi fare cose davvero interessanti.


devo amare queste funzioni che restituiscono sempre true;) Presumo che questo sia immune da problemi di ordinamento statico di inizializzazione?
paulm

14

Consiglierei di usare Qt .

Esiste una licenza open-source e una licenza commerciale.


1
Ho esaminato questo ma utilizza macro e il codice sorgente deve essere analizzato per generare il codice dei metadati. Vorrei evitare questo passaggio aggiuntivo. Preferisco usare una libreria C ++ o semplici macro. Grazie per l'idea però.
Nick

10
QT, o un'altra libreria che implementa un approccio simile è il meglio che otterrai
jalf

5
Paga in fase di compilazione o paga in fase di esecuzione - in entrambi i casi stai pagando!
Martin Beckett

13

Cosa stai cercando di fare con la riflessione?
È possibile utilizzare i tratti del tipo Boost e le librerie typeof come una forma limitata di riflessione in fase di compilazione. In altre parole, è possibile esaminare e modificare le proprietà di base di un tipo passato a un modello.


13

EDIT : CAMP non è più mantenuto; sono disponibili due forchette:

  • Uno è anche chiamato CAMP e si basa sulla stessa API.
  • Ponder è una riscrittura parziale e deve essere preferita in quanto non richiede Boost; sta usando C ++ 11.

CAMP è una libreria con licenza MIT (precedentemente LGPL) che aggiunge riflessioni al linguaggio C ++. Non richiede uno specifico passaggio di pre-elaborazione nella compilazione, ma l'associazione deve essere eseguita manualmente.

L'attuale libreria Tegesoft utilizza Boost, ma esiste anche un fork che utilizza C ++ 11 che non richiede più Boost .


11

Ho fatto qualcosa di simile a quello che cercavi una volta e, sebbene sia possibile ottenere un certo livello di riflessione e accedere a funzionalità di livello superiore, il mal di testa per la manutenzione potrebbe non valerne la pena. Il mio sistema è stato utilizzato per mantenere le classi dell'interfaccia utente completamente separate dalla logica aziendale tramite delega simile al concetto di passaggio e inoltro dei messaggi di Objective-C. Il modo per farlo è creare una classe base che sia in grado di mappare simboli (ho usato un pool di stringhe ma potresti farlo con enumerazioni se preferisci la velocità e la gestione degli errori in fase di compilazione rispetto alla flessibilità totale) ai puntatori a funzione (in realtà no puntatori a funzione pura, ma qualcosa di simile a quello che Boost ha con Boost.Function, a cui non avevo accesso al momento). Puoi fare la stessa cosa per le tue variabili membro purché tu abbia una classe base comune in grado di rappresentare qualsiasi valore. L'intero sistema era una sfacciata imbroglio della codifica valore-chiave e della delega, con alcuni effetti collaterali che forse valevano l'enorme quantità di tempo necessario per far sì che ogni classe che utilizzava il sistema abbinasse tutti i suoi metodi e membri con chiamate legali : 1) Qualsiasi classe può chiamare qualsiasi metodo su qualsiasi altra classe senza dover includere intestazioni o scrivere classi base false in modo che l'interfaccia possa essere predefinita per il compilatore; e 2) I getter e setter delle variabili membro erano facili da rendere thread-safe perché la modifica o l'accesso ai loro valori veniva sempre eseguita tramite 2 metodi nella classe base di tutti gli oggetti. L'intero sistema era una sfacciata imbroglio della codifica valore-chiave e della delega, con alcuni effetti collaterali che forse valevano l'enorme quantità di tempo necessario per far sì che ogni classe che utilizzava il sistema abbinasse tutti i suoi metodi e membri con chiamate legali : 1) Qualsiasi classe può chiamare qualsiasi metodo su qualsiasi altra classe senza dover includere intestazioni o scrivere classi base false in modo che l'interfaccia possa essere predefinita per il compilatore; e 2) I getter e setter delle variabili membro erano facili da rendere thread-safe perché la modifica o l'accesso ai loro valori veniva sempre eseguita tramite 2 metodi nella classe base di tutti gli oggetti. L'intero sistema era una sfacciata imbroglio della codifica valore-chiave e della delega, con alcuni effetti collaterali che forse valevano l'enorme quantità di tempo necessario per far sì che ogni classe che utilizzava il sistema abbinasse tutti i suoi metodi e membri con chiamate legali : 1) Qualsiasi classe può chiamare qualsiasi metodo su qualsiasi altra classe senza dover includere intestazioni o scrivere classi base false in modo che l'interfaccia possa essere predefinita per il compilatore; e 2) I getter e setter delle variabili membro erano facili da rendere thread-safe perché la modifica o l'accesso ai loro valori veniva sempre eseguita tramite 2 metodi nella classe base di tutti gli oggetti. 1) Qualsiasi classe può chiamare qualsiasi metodo su qualsiasi altra classe senza dover includere intestazioni o scrivere classi base false in modo che l'interfaccia possa essere predefinita per il compilatore; e 2) I getter e setter delle variabili membro erano facili da rendere thread-safe perché la modifica o l'accesso ai loro valori veniva sempre eseguita tramite 2 metodi nella classe base di tutti gli oggetti. 1) Qualsiasi classe potrebbe chiamare qualsiasi metodo su qualsiasi altra classe senza dover includere intestazioni o scrivere classi base false in modo che l'interfaccia possa essere predefinita per il compilatore; e 2) I getter e setter delle variabili membro erano facili da rendere thread-safe perché la modifica o l'accesso ai loro valori veniva sempre eseguita tramite 2 metodi nella classe base di tutti gli oggetti.

Ha anche portato alla possibilità di fare alcune cose davvero strane che altrimenti non sarebbero facili in C ++. Ad esempio, potrei creare un oggetto Array che contenga elementi arbitrari di qualsiasi tipo, incluso se stesso, e creare nuovi array dinamicamente passando un messaggio a tutti gli elementi dell'array e raccogliendo i valori di ritorno (simile alla mappa in Lisp). Un altro era l'implementazione dell'osservazione del valore-chiave, per cui ero in grado di impostare l'interfaccia utente per rispondere immediatamente alle modifiche nei membri delle classi di backend invece di eseguire costantemente il polling dei dati o ridisegnare inutilmente la visualizzazione.

Forse più interessante per te è il fatto che puoi anche scaricare tutti i metodi e i membri definiti per una classe, e nientemeno in forma di stringa.

Aspetti negativi del sistema che potrebbero scoraggiarti dal preoccuparti: aggiungere tutti i messaggi e i valori-chiave è estremamente noioso; è più lento che senza alcun riflesso; crescerai fino a odiare la vista boost::static_pointer_caste in boost::dynamic_pointer_casttutto il tuo codice con una violenta passione; i limiti del sistema fortemente tipizzato sono ancora presenti, in realtà li stai solo nascondendo un po ', quindi non è così ovvio. Anche gli errori di battitura nelle corde non sono una sorpresa divertente o facile da scoprire.

Riguardo a come implementare qualcosa di simile: usa solo puntatori condivisi e deboli a qualche base comune (il mio era chiamato in modo molto fantasioso "Object") e deriva per tutti i tipi che vuoi usare. Consiglierei di installare Boost.Function invece di farlo come ho fatto io, che era con qualche cazzata personalizzata e un sacco di brutte macro per avvolgere le chiamate del puntatore di funzione. Poiché tutto è mappato, ispezionare gli oggetti è solo questione di iterare attraverso tutte le chiavi. Dal momento che le mie classi erano essenzialmente il più vicino possibile a un imbroglio diretto di Cocoa usando solo C ++, se vuoi qualcosa del genere, suggerirei di utilizzare la documentazione di Cocoa come modello.


Ehi, @Michael; hai ancora il codice sorgente per questo o te ne sei sbarazzato? Vorrei dare un'occhiata se non ti dispiace.
RandomDSdevel

Ops, hai scritto male il tuo nome! Non c'è da stupirsi se non ho mai ricevuto una risposta ...
RandomDSdevel

10

C'è un'altra nuova libreria per la riflessione in C ++, chiamata RTTR (Run Time Type Reflection, vedi anche github ).

L'interfaccia è simile alla reflection in C # e funziona senza RTTI.


8

Le due soluzioni simili a riflessioni che conosco dai miei giorni in C ++ sono:

1) Usa RTTI, che fornirà un bootstrap per costruire il tuo comportamento simile a un riflesso, se sei in grado di far derivare tutte le tue classi da una classe base "oggetto". Quella classe potrebbe fornire alcuni metodi come GetMethod, GetBaseClass ecc. Per quanto riguarda il funzionamento di questi metodi, dovrai aggiungere manualmente alcune macro per decorare i tuoi tipi, che dietro le quinte creano metadati nel tipo per fornire risposte a GetMethods ecc.

2) Un'altra opzione, se si ha accesso agli oggetti del compilatore, è utilizzare DIA SDK . Se ricordo bene, questo ti consente di aprire pdbs, che dovrebbe contenere metadati per i tuoi tipi C ++. Potrebbe essere sufficiente per fare ciò di cui hai bisogno. Questa pagina mostra, ad esempio, come ottenere tutti i tipi di base di una classe.

Entrambe queste soluzioni sono un po 'brutte però! Non c'è niente come un po 'di C ++ per farti apprezzare i lussi di C #.

In bocca al lupo.


Questo è furbo e un trucco gigante, con la cosa DIA SDK che hai suggerito lì.
Sqeaky

6

Questa domanda è un po 'vecchia ora (non so perché continuo a rispondere a vecchie domande oggi) ma stavo pensando a BOOST_FUSION_ADAPT_STRUCT che introduce la riflessione in fase di compilazione.

Spetta a te mappare questo alla riflessione in fase di esecuzione, ovviamente, e non sarà troppo facile, ma è possibile in questa direzione, mentre non sarebbe al contrario :)

Penso davvero che una macro per incapsulare BOOST_FUSION_ADAPT_STRUCTquella possa generare i metodi necessari per ottenere il comportamento di runtime.


2
di minghua (che originariamente ha modificato il post): ho scavato in questa soluzione BOOST_FUSION_ADAPT_STRUCT e alla fine ho trovato un esempio. Vedi questa nuova domanda SO - C ++ itera nel campo struct annidato con boost fusion adapt_struct .
Matthieu M.

Fantastico, Matthieu! Mi sono appena reso conto di aver visto i tuoi suggerimenti qua e là nel corso dell'ultimo anno. Non ho notato che sono imparentati fino ad ora. Quelli sono stati molto stimolanti.
minghua

6

EDIT: collegamento interrotto aggiornato a partire dal 7 febbraio 2017.

Penso che nessuno lo abbia menzionato:

Al CERN usano un sistema di riflessione completo per C ++:

Riflesso del CERN . Sembra funzionare molto bene.


@ j4nbur53 Il collegamento è interrotto perché sembra che abbiano raggiunto una pietra miliare: root.cern.ch
Germán Diago

Potrebbe essere che intendi questo link root.cern.ch/root/doc/ROOTUsersGuideHTML/ch07.html Chapter Reflex?
Mostowski Crollo

Prova questo root.cern.ch/how/how-use-reflex . Reflex funziona come un generatore che analizza i file di intestazione e genera codice / libreria di introspezione c ++, a cui è possibile collegarsi e utilizzare una semplice API.
Adam Ryczkowski

6

Penso che potresti trovare interessante l'articolo "Utilizzo dei modelli per la riflessione in C ++" di Dominic Filion. È nella sezione 1.4 di Game Programming Gems 5 . Purtroppo non ho la mia copia con me, ma cercala perché penso che spieghi quello che stai chiedendo.


4

Ponder è una libreria di riflessione C ++, in risposta a questa domanda. Ho considerato le opzioni e ho deciso di crearne una mia poiché non riuscivo a trovarne una che soddisfacesse tutte le mie caselle.

Sebbene ci siano ottime risposte a questa domanda, non voglio usare tonnellate di macro o fare affidamento su Boost. Boost è un'ottima libreria, ma ci sono molti piccoli progetti C ++ 0x su misura che sono più semplici e hanno tempi di compilazione più rapidi. Ci sono anche vantaggi nell'essere in grado di decorare una classe esternamente, come il wrapping di una libreria C ++ che non supporta (ancora?) C ++ 11. È il fork di CAMP, utilizzando C ++ 11, che non richiede più Boost .


4

La riflessione riguarda essenzialmente ciò che il compilatore ha deciso di lasciare come impronte nel codice che il codice di runtime può interrogare. Il C ++ è famoso per non pagare ciò che non usi; poiché la maggior parte delle persone non usa / vuole la riflessione, il compilatore C ++ evita i costi non registrando nulla .

Quindi, C ++ non fornisce la riflessione, e non è facile "simularla" da soli come regola generale, come hanno notato altre risposte.

In "altre tecniche", se non hai un linguaggio con riflessione, procurati uno strumento in grado di estrarre le informazioni che desideri in fase di compilazione.

Il nostro DMS Software Reengineering Toolkit è una tecnologia di compilazione generalizzata parametrizzata da definizioni linguistiche esplicite. Ha definizioni linguistiche per C, C ++, Java, COBOL, PHP, ...

Per le versioni C, C ++, Java e COBOL, fornisce l'accesso completo agli alberi di analisi e alle informazioni sulla tabella dei simboli. Le informazioni sulla tabella dei simboli includono il tipo di dati che è probabile che tu voglia dalla "riflessione". Se l'obiettivo è enumerare un insieme di campi o metodi e fare qualcosa con essi, DMS può essere utilizzato per trasformare il codice in base a ciò che si trova nelle tabelle dei simboli in modi arbitrari.


3

Puoi trovare un'altra libreria qui: http://www.garret.ru/cppreflection/docs/reflect.html Supporta 2 modi: ottenere informazioni sul tipo dalle informazioni di debug e lasciare che il programmatore fornisca queste informazioni.

Mi interessava anche la riflessione per il mio progetto e ho trovato questa libreria, non l'ho ancora provata, ma ho provato altri strumenti di questo ragazzo e mi piace come funzionano :-)


3

Dai un'occhiata a Classdesc http://classdesc.sf.net . Fornisce una riflessione sotto forma di "descrittori" di classe, funziona con qualsiasi compilatore C ++ standard (sì, è noto per funzionare con Visual Studio e GCC) e non richiede annotazioni del codice sorgente (sebbene esistano alcuni pragmi per gestire situazioni difficili ). È in fase di sviluppo da più di un decennio e viene utilizzato in numerosi progetti su scala industriale.


1
Benvenuto in Stack Overflow. Sebbene questa risposta sia sull'argomento, è importante sottolineare che sei l'autore di questo software, per chiarire che non è una raccomandazione imparziale :-)
Matthew Strawbridge

2

Quando volevo riflettere in C ++, ho letto questo articolo e ho migliorato ciò che ho visto lì. Spiacente, nessuna lattina ha. Non possiedo il risultato ... ma puoi sicuramente ottenere quello che ho avuto e partire da lì.

Attualmente sto ricercando, quando ne ho voglia, metodi da utilizzare inherit_linearly per rendere molto più semplice la definizione di tipi riflettibili. In realtà sono andato abbastanza lontano, ma ho ancora molta strada da fare. È molto probabile che le modifiche in C ++ 0x siano di grande aiuto in quest'area.


2

Sembra che il C ++ non abbia ancora questa funzionalità. E anche C ++ 11 ha posticipato la riflessione ((

Cerca alcune macro o creane di tue. Qt può anche aiutare con la riflessione (se può essere usato).


2

anche se la riflessione non è supportata immediatamente in c ++, non è troppo difficile da implementare. Ho incontrato questo fantastico articolo: http://replicaisland.blogspot.co.il/2010/11/building-reflective-object-system-in-c.html

l'articolo spiega in modo molto dettagliato come implementare un sistema di riflessione piuttosto semplice e rudimentale. ammesso che non sia la soluzione più salutare, e ci sono degli spigoli da sistemare ma per le mie esigenze era sufficiente.

la linea di fondo - la riflessione può ripagare se eseguita correttamente ed è completamente fattibile in c ++.


2

Vorrei pubblicizzare l'esistenza del toolkit automatico di introspezione / riflessione "IDK". Utilizza un meta-compilatore come Qt e aggiunge meta informazioni direttamente nei file oggetto. Si afferma che sia facile da usare. Nessuna dipendenza esterna. Ti consente anche di riflettere automaticamente std :: string e quindi di usarlo negli script. Per favore guarda IDK


2

Se stai cercando una riflessione C ++ relativamente semplice, ho raccolto da varie fonti macro / definizioni e le ho commentate come funzionano. Puoi scaricare i file di intestazione da qui:

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

insieme di definizioni, più funzionalità sopra di esso:

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

Anche l'applicazione di esempio risiede nel repository git, qui: https://github.com/tapika/TestCppReflect/

Lo copierò in parte qui con una spiegazione:

#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 il nome della classe + il nome del campo con offsetof- per identificare in quale punto della memoria si trova un particolare campo. Ho provato a prendere la terminologia .NET per quanto possibile, ma C ++ e C # sono diversi, quindi non è 1 a 1. L'intero modello di riflessione C ++ risiede in TypeInfoe FieldInfoclassi.

Ho usato il parser pugi xml per recuperare il codice demo in xml e ripristinarlo da xml.

Quindi l'output prodotto dal codice demo assomiglia a questo:

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

È anche possibile abilitare qualsiasi supporto di classe / struttura di terze parti tramite la classe TypeTraits e la specifica parziale del modello - per definire la propria classe TypeTraitsT, in modo simile a CString o int - vedere il codice di esempio in

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

Questa soluzione è applicabile per Windows / Visual Studio. È possibile portarlo su altri sistemi operativi / compilatori, ma non l'ho fatto. (Chiedimi se ti piace davvero la soluzione, potrei essere in grado di aiutarti)

Questa soluzione è applicabile per la serializzazione one shot di una classe con più sottoclassi.

Se tuttavia stai cercando un meccanismo per serializzare le parti di una classe o anche per controllare ciò che le chiamate di riflessione della funzionalità producono, potresti dare un'occhiata alla seguente soluzione:

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

Informazioni più dettagliate possono essere trovate dal video di YouTube:

Riflessione del tipo di runtime C ++ https://youtu.be/TN8tJijkeFE

Sto cercando di spiegare un po 'più a fondo come funzionerà la riflessione in c ++.

Il codice di esempio sarà simile a questo:

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;

Ma ogni passaggio qui si traduce effettivamente in una chiamata di funzione Utilizzo delle proprietà C ++ con __declspec(property(get =, put ... ).

che riceve informazioni complete su tipi di dati C ++, nomi di proprietà C ++ e puntatori di istanze di classe, sotto forma di percorso, e in base a tali informazioni è possibile generare xml, json o persino serializzarlo su Internet.

Esempi di tali funzioni di callback virtuale possono essere trovati qui:

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

Vedi funzioni ReflectCopye funzione virtuale ::OnAfterSetProperty.

Ma poiché l'argomento è molto avanzato, consiglio di controllare prima il video.

Se hai qualche idea di miglioramento, non esitare a contattarmi.


2

La libreria RareCpp consente una riflessione abbastanza semplice e intuitiva: tutte le informazioni sul campo / tipo sono progettate per essere disponibili in array o per sembrare un accesso ad array. È scritto per C ++ 17 e funziona con Visual Studios, g ++ e Clang. La libreria è solo di intestazione, il che significa che devi solo copiare "Reflect.h" nel tuo progetto per usarlo.

Le strutture o le classi riflesse richiedono la macro REFLECT, in cui fornisci il nome della classe che stai riflettendo ei nomi dei campi.

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

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

Questo è tutto ciò che c'è, non è necessario alcun codice aggiuntivo per impostare la riflessione. Facoltativamente è possibile fornire superclassi (tra parentesi del primo argomento) e annotazioni di campo (tra parentesi che precedono il campo che si desidera annotare) per poter attraversare le superclassi o aggiungere ulteriori informazioni in fase di compilazione a un campo (come Json: :Ignorare).

Il ciclo tra i campi può essere semplice come ...

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

È possibile scorrere un'istanza di un oggetto per accedere ai valori del campo (che è possibile leggere o modificare) e alle informazioni sul tipo di 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;
});

Una libreria JSON è costruita sopra RandomAccessReflection che identifica automaticamente le rappresentazioni di output JSON appropriate per la lettura o la scrittura e può attraversare in modo ricorsivo tutti i campi riflessi, nonché array e contenitori STL.

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

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

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

Quanto sopra potrebbe essere eseguito in questo modo ...

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

Guarda anche...


1

La riflessione in C ++ è molto utile, nei casi in cui è necessario eseguire un metodo per ogni membro (ad esempio: serializzazione, hashing, confronto). Sono arrivato con una soluzione generica, con una sintassi molto semplice:

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

Dove ENUMERATE_MEMBERS è una macro, descritta più avanti (UPDATE):

Supponiamo di aver definito la funzione di serializzazione per int e std :: string in questo modo:

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 abbiamo una funzione generica vicino alla "macro segreta";)

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

Adesso puoi scrivere

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)

Quindi, avendo la macro ENUMERATE_MEMBERS nella definizione della struttura, puoi creare serializzazione, confronto, hashing e altri elementi senza toccare il tipo originale, l'unico requisito è implementare il metodo "EnumerateWith" per ogni tipo, che non è enumerabile, per enumeratore (come BinaryWriter) . Di solito dovrai implementare 10-20 tipi "semplici" per supportare qualsiasi tipo nel tuo progetto.

Questa macro dovrebbe avere zero overhead per la creazione / distruzione della struttura in fase di esecuzione e il codice di T.EnumerateWith () dovrebbe essere generato su richiesta, cosa che può essere ottenuta rendendola funzione template-inline, quindi l'unico overhead in tutta la storia è aggiungere ENUMERATE_MEMBERS (m1, m2, m3 ...) a ogni struttura, mentre l'implementazione di un metodo specifico per tipo di membro è un must in qualsiasi soluzione, quindi non lo presumo come overhead.

AGGIORNAMENTO: esiste un'implementazione molto semplice della macro ENUMERATE_MEMBERS (tuttavia potrebbe essere leggermente estesa per supportare l'ereditarietà da strutture enumerabili)

#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 non hai bisogno di alcuna libreria di terze parti per queste 15 righe di codice;)


1

Puoi ottenere fantastiche funzionalità di riflessione statica per le strutture con BOOST_HANA_DEFINE_STRUCT dalla libreria Boost :: Hana.
Hana è abbastanza versatile, non solo per il caso d'uso che hai in mente, ma anche per molti modelli di metaprogrammazione.


0

Se dichiari un puntatore a una funzione come questa:

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

Puoi assegnare un posto in memoria a quella funzione in questo modo (richiede 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);
}

Per caricare un simbolo locale utilizzando l'indirizzamento indiretto, è possibile utilizzare dlopensulla chiamata binary ( argv[0]).

L'unico requisito per questo (diverso da dlopen(), libdle dlfcn.h) è conoscere gli argomenti e il tipo della funzione.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.