Domanda

Mi piacerebbe essere in grado di analizzare una classe C++ per il suo nome, i suoi contenuti (ad es.membri e le loro tipologie) ecc.Sto parlando di C++ nativo qui, non di C++ gestito, che ha una riflessione.Mi rendo conto che C++ fornisce alcune informazioni limitate utilizzando RTTI.Quali librerie aggiuntive (o altre tecniche) potrebbero fornire queste informazioni?

È stato utile?

Soluzione 17

Riflettere è una libreria di riflessione C++, in risposta a questa domanda.Ho considerato le opzioni e ho deciso di crearne uno mio poiché non sono riuscito a trovarne uno che soddisfacesse tutte le mie esigenze.

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

Altri suggerimenti

Ciò che devi fare è fare in modo che il preprocessore generi dati di riflessione sui campi.Questi dati possono essere archiviati come classi nidificate.

Innanzitutto, per rendere più semplice e pulita la scrittura nel preprocessore, utilizzeremo l'espressione tipizzata.Un'espressione tipizzata è semplicemente un'espressione che mette il tipo tra parentesi.Quindi invece di scrivere int x scriverai (int) x.Ecco alcune utili macro che aiutano 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 a REFLECTABLE macro per generare i dati su ciascun campo (più il campo stesso).Questa macro si chiamerà così:

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

Quindi usando Potenzia.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_n questo è il numero di campi riflettebili nella classe.Poi si specializza il field_data per ciascun campo.È anche amico di reflector class, 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 scorrere i campi utilizziamo il pattern visitatore.Creiamo un intervallo MPL da 0 al numero di campi e accediamo ai dati del campo su quell'indice.Quindi trasmette 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));
}

Ora, per il momento della verità, mettiamo tutto insieme.Ecco come possiamo definire a Person classe riflettebile:

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

Ecco una generalizzazione print_fields funzione che utilizza i dati di riflessione per scorrere i 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_fields con il riflettebile Person classe:

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

Quali output:

name=Tom
age=82

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

Ce ne sono due tipi reflection nuotare in giro.

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

    Questo non è possibile con C++.
  2. Ispezione controllando se un tipo di classe (classe, struttura, unione) ha un metodo o un tipo annidato, deriva da un altro tipo particolare.

    Questo genere di cose è possibile utilizzando C++ template-tricks.Utilizzo boost::type_traits per molte cose (come verificare 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 determinato tipo annidato, utilizzare plain SFINE .

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 esista un modo C++ standard per farlo.Devi usare entrambi

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

Il C++ è realizzato pensando alla velocità.Se desideri un'ispezione di alto livello, come ha fatto C# o Java, allora temo di doverti dire che non è possibile farlo senza un certo sforzo.

E mi piacerebbe un pony, ma i pony non sono liberi.:-P

http://en.wikibooks.org/wiki/C%2B%2B_Programming/RTTI è quello che otterrai.La riflessione a cui stai pensando - metadati completamente descrittivi disponibili in fase di esecuzione - semplicemente non esiste per C++ per impostazione predefinita.

RTTI non esiste per C++.

Questo è semplicemente sbagliato.In realtà, il termine stesso “RTTI” è stato coniato dallo standard C++.D'altra parte, RTTI non va molto lontano nell'implementazione della riflessione.

Le informazioni esistono, ma non nel formato che ti serve e solo se esporti le tue lezioni.Funziona su Windows, non conosco altre piattaforme.Utilizzando gli specificatori della classe di archiviazione come in, 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 facilmente per la riflessione.

Nella mia azienda abbiamo creato una libreria che interpreta questi metadati e ti consente di riflettere una classe senza inserire macro aggiuntive, ecc.nella classe stessa.Permette di richiamare le funzioni nel modo seguente:

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

Questo effettivamente fa:

instance_ptr->Foo(1.331);

La funzione Invoke(this_pointer,...) ha argomenti variabili.Ovviamente chiamando una funzione in questo modo si eludono cose come const-safety e così via, quindi questi aspetti vengono 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++, eseguire lo streaming da e verso XML e così via, e non è necessario derivare da una classe base specifica.Se ci fosse abbastanza domanda forse potremmo metterlo in forma per il rilascio.

Devi esaminare cosa stai cercando di fare e se RTTI soddisferà le tue esigenze.Ho implementato la mia pseudo-riflessione per alcuni scopi molto specifici.Ad esempio, una volta volevo poter configurare in modo flessibile il risultato di una simulazione.Era necessario aggiungere del codice standard alle classi che sarebbero state generate:

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 file 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 alcuni modelli magici che coinvolgono boost, questo viene tradotto in una serie di chiamate al metodo in fase di esecuzione (quando viene letto il file di configurazione), quindi è abbastanza efficiente.Non consiglierei di farlo a meno che non sia realmente necessario, ma, quando lo fai, puoi fare delle cose davvero interessanti.

Cosa stai cercando di fare con la riflessione?
Puoi usare il potenziamento tratti di tipo E tipo di librerie come forma limitata di riflessione in fase di compilazione.Cioè, puoi controllare e modificare le proprietà di base di un tipo passato a un modello.

Consiglierei l'uso Qt.

Esiste una licenza open source e una licenza commerciale.

MODIFICARE: CAMPO non è più mantenuto;sono disponibili due forcelle:

  • Uno è anche chiamato CAMPO anch'esso e si basa sulla stessa API.
  • Riflettere è una riscrittura parziale e sarà preferibile in quanto non richiede Boost ;sta usando C++ 11.

CAMPO è una libreria con licenza MIT (precedentemente LGPL) che aggiunge riflessione al linguaggio C++.Non richiede uno specifico passaggio di preelaborazione nella compilazione, ma il collegamento deve essere effettuato manualmente.

L'attuale libreria Tegesoft utilizza Boost, ma c'è anche una forchetta usando C++ 11 quello non richiede più Boost.

Una volta ho fatto qualcosa di simile a quello che stavi cercando e, sebbene sia possibile ottenere un certo livello di riflessione e accesso a funzionalità di livello superiore, il mal di testa della manutenzione potrebbe non valerne la pena.Il mio sistema è stato utilizzato per mantenere le classi dell'interfaccia utente completamente separate dalla logica aziendale attraverso la delega simile al concetto di passaggio e inoltro di messaggi di Objective-C.Il modo per farlo è creare una classe base 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) a puntatori a funzioni (in realtà non puntatori a funzioni pure, ma qualcosa di simile a ciò che Boost ha con Boost.Function, a cui non avevo accesso in quel 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 copiatura della codifica e della delega dei valori-chiave, con alcuni effetti collaterali che forse valevano la quantità di tempo necessaria per convincere ogni classe che utilizzava il sistema ad abbinare 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 i setter delle variabili membro erano facili da rendere thread-safe perché la modifica o l'accesso ai loro valori veniva sempre eseguito tramite 2 metodi nella classe base di tutti gli oggetti.

Ha portato anche alla possibilità di fare 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 restituiti (simile alla mappa in Lisp).Un'altra è stata l'implementazione dell'osservazione dei valori-chiave, grazie alla quale sono stato in grado di impostare l'interfaccia utente per rispondere immediatamente ai cambiamenti nei membri delle classi backend invece di interrogare costantemente i dati o ridisegnare inutilmente la visualizzazione.

Forse più interessante per te è il fatto che puoi anche eseguire il dump di tutti i metodi e i membri definiti per una classe, e niente meno che sotto 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 alcuna riflessione;finirai per odiare vedere boost::static_pointer_cast E boost::dynamic_pointer_cast su tutta la tua base di codice con una passione violenta;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 stringhe non sono una sorpresa divertente o facile da scoprire.

Per quanto riguarda come implementare qualcosa del genere:usa semplicemente puntatori condivisi e deboli a qualche base comune (il mio era chiamato in modo molto fantasioso "Oggetto") e deriva per tutti i tipi che vuoi usare.Consiglierei di installare Boost.Function invece di farlo come ho fatto io, ovvero con qualche schifezza personalizzata e un sacco di brutte macro per racchiudere le chiamate del puntatore a funzione.Poiché tutto è mappato, ispezionare gli oggetti è solo questione di scorrere tutte le chiavi.Dato che le mie lezioni erano essenzialmente il più vicino possibile a una copia diretta di Cocoa utilizzando solo C++, se desideri qualcosa del genere, suggerirei di utilizzare la documentazione di Cocoa come modello.

Le due soluzioni di riflessione che conosco dai miei giorni in C++ sono:

1) Usa RTTI, che ti fornirà un bootstrap per costruire il tuo comportamento simile alla riflessione, se riesci a 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 hai accesso agli oggetti del compilatore, è usare il file SDK DIA.Se ricordo bene questo ti consente di aprire pdbs, che dovrebbe contenere metadati per i tuoi tipi C++.Potrebbe essere sufficiente fare ciò di cui hai bisogno. Questa pagina mostra ad esempio come ottenere tutti i tipi base di una classe.

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

Buona fortuna.

C'è un'altra nuova libreria per la riflessione in C++, chiamata RTTR (Riflessione del tipo Run Time, vedere anche github).

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

MODIFICARE:Link non funzionante aggiornato al 7 febbraio 2017.

Penso che nessuno abbia menzionato questo:

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

Riflesso del CERN.Sembra funzionare molto bene

La riflessione non è supportata immediatamente da C++.Questo è triste perché rende i test difensivi una seccatura.

Esistono diversi approcci per riflettere:

  1. utilizzare le informazioni di debug (non portatili).
  2. Cospargi il tuo codice con macro/modelli o qualche altro approccio sorgente (sembra brutto)
  3. Modificare un compilatore come clang/gcc per produrre un database.
  4. Utilizzare l'approccio Qt moc
  5. Aumenta la riflessione
  6. Riflessione precisa e piatta

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

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

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

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

Ora esiste un gruppo di lavoro per la riflessione sul C++.Vedi le novità per C++14 al CERN:

Modifica 13/08/17:Rispetto al post originale ci sono stati numerosi potenziali progressi nella riflessione.Di seguito vengono forniti maggiori dettagli e una discussione sulle varie tecniche e sullo stato:

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

Tuttavia non sembra promettente 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 della riflessione in C++.

Di seguito viene riportato in dettaglio lo stato attuale in base al feedback dell'ultimo incontro sugli 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

Una bozza di TS è stata inviata agli organi nazionali per il voto.

Il testo lo trovate qui: https://github.com/cplusplus/reflection-ts

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

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

Penso davvero che una macro incapsula il file BOOST_FUSION_ADAPT_STRUCT si potrebbero generare i metodi necessari per ottenere il comportamento di runtime.

Penso che potresti trovare interessante l'articolo "Using Templates for Reflection in C++" di Dominic Filion.Si trova nella sezione 1.4 del Gemme della programmazione di giochi 5.Sfortunatamente non ho la mia copia con me, ma cercala perché penso che spieghi cosa stai chiedendo.

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

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

Nella sezione "altre tecniche", se non hai un linguaggio riflessivo, ottieni uno strumento in grado di estrarre le informazioni desiderate in fase di compilazione.

Nostro Kit di strumenti di reingegnerizzazione del software DMS è una tecnologia di compilazione generalizzata parametrizzata da definizioni di linguaggio 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 probabilmente desideri dalla "riflessione".Se il tuo obiettivo è enumerare alcuni insiemi di campi o metodi e Fare qualcosa con loro, DMS può essere utilizzato per trasformare il codice in base a ciò che trovi nelle tabelle dei simboli in modi arbitrari.

Puoi trovare un'altra libreria qui: http://www.garret.ru/cppreflection/docs/reflect.htmlSupporta 2 modi:ottenere informazioni sul tipo dalle informazioni di debug e consentire al programmatore di fornire 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 :-)

Scopri Classdesc http://classdesc.sf.net.Fornisce riflessione sotto forma di "descrittori" di classe, funziona con qualsiasi compilatore C++ standard (sì, è noto che funziona con Visual Studio e GCC) e non richiede annotazioni sul codice sorgente (sebbene esistano alcuni pragma per gestire situazioni complicate ).È in fase di sviluppo da oltre un decennio e utilizzato in numerosi progetti su scala industriale.

Quando volevo la riflessione in C++ ho letto Questo articolo e migliorato ciò che ho visto lì.Siamo spiacenti, non è possibile.Non possiedo il risultato... ma puoi certamente ottenere quello che avevo io e partire da lì.

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

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

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

Prova a dare un'occhiata a questo progetto http://www.garret.ru/cppreflection/docs/reflect.htmlvengono aggiunte riflessioni al C++.Ha aggiunto metadati alle classi che puoi quindi utilizzare.

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

l'articolo spiega in grande dettaglio come implementare un sistema di riflessione piuttosto semplice e rudimentale.concesso che non sia la soluzione più salutare, e ci sono ancora degli spigoli da risolvere, ma per le mie esigenze era sufficiente.

in conclusione: la riflessione può ripagare se eseguita correttamente ed è completamente fattibile in C++.

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

La riflessione in C++ è molto utile, nei casi in cui è necessario eseguire qualche metodo per ciascun 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
}

Ora 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 spese generali per strutturare la creazione/distruzione in fase di esecuzione e il codice di T.EnumerateWith() dovrebbe essere generato su richiesta, cosa che può essere ottenuta rendendola funzione in linea di modello, quindi l'unico sovraccarico in tutta la storia è aggiungere ENUMERATE_MEMBERS(m1,m2,m3...) a ciascuna struttura, mentre l'implementazione di un metodo specifico per tipo di membro è un must in qualsiasi soluzione, quindi non lo presumo come sovraccarico.

AGGIORNAMENTO:Esiste un'implementazione molto semplice della macro ENUMERATE_MEMBERS (tuttavia potrebbe essere leggermente estesa per supportare l'ereditarietà dalla struttura enumerabile)

#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 librerie di terze parti per queste 15 righe di codice ;)

Con C++20, hai dichiarazioni di espansione, che consente di eseguire iterazioni sui tipi di aggregati:

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 stai cercando una riflessione C++ relativamente semplice, ho raccolto macro/definizioni da varie fonti e le ho commentate su come funzionano.Puoi scaricare file di intestazione da qui:

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

insieme di definizioni, più funzionalità su 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

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

Lo copierò in parte qui con la 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 );

}

REFLECTABLE define utilizza il nome della classe + il nome del campo con offsetof - identificare in quale luogo della memoria si trova un particolare campo.Ho provato a riprendere il più possibile la terminologia .NET, ma C++ e C# sono diversi, quindi non è 1 a 1.Risiede l'intero modello di riflessione C++ TypeInfo E FieldInfo classi.

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

Quindi l'output prodotto dal codice demo è simile al seguente:

<?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 per classi/strutture 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 trasferirlo 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 parti di classe o anche per controllare quali funzionalità producono le chiamate di riflessione, 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ù in profondità come funzionerà la riflessione C++.

Il codice di esempio sarà simile, ad esempio, 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 passo qui si traduce effettivamente in una chiamata di funzione usando le proprietà C ++ con __declspec(property(get =, put ... ).

che riceve informazioni complete sui tipi di dati C++, nomi di proprietà C++ e puntatori a 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 ReflectCopy, e funzione virtuale ::OnAfterSetProperty.

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

Se avete qualche idea di miglioramento, non esitate a contattarmi.

Il progetto Root Reflex ha il supporto per questo.

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

Se dichiari un puntatore a una funzione come questa:

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

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

Per caricare un simbolo locale utilizzando l'indiretto, è possibile utilizzare dlopen sul binario chiamante (argv[0]).

L'unico requisito per questo (diverso da dlopen(), libdl, E dlfcn.h) sta conoscendo gli argomenti e il tipo della funzione.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top