Come calcolare l’offset di un membro della classe a in fase di compilazione?

Dato una definizione di classe in C++

class A
{
  public:
    //methods definition
    ....

  private:
    int i;
    char *str;
    ....
}

È possibile calcolare l’offset di un membro della classe a in fase di compilazione utilizzando C++ modello di meta-programmazione? La classe non è POD, e può disporre di metodi virtuali, primitivo e dei dati oggetto membro.

  • Cosa intendi esattamente per “offset di un membro della classe”? Vuoi dire quanti byte è necessario aggiungere un puntatore a un’istanza della classe (dopo, per dire, reinterpret_cast‘ing a char *) per raggiungere i membri? Se è così, non sarebbe un semplice sottrazione dire?
  • Si potrebbe utilizzare offsetof(A, i) se che sono stati definiti per tali tipi. Controllare la documentazione del compilatore per vedere se è.
  • Qui è il link per il codice di esempio che utilizza offsetof().
  • e Hindol: Perché le funzionalità estese di strutture in C++, in questa lingua, l’uso di offsetof è limitato a “tipi di CONTENITORE”, che per le classi, più o meno corrisponde al C concetto di struct (anche se non le classi derivate con solo pubblico non virtuale funzioni membro e con nessun costruttore e/o distruttore qualificherebbero anche come CONTENITORE).
  • Concordato, così il congiuntivo nella mia frase “se che sono definito”.
  • Schwartz: Sì, questo può essere calcolata in fase di runtime.
  • se non è definita, di definire da soli 😉
  • Ricordo che il offsetof non è parte dello standard, ma ancora che vi sembrano guardare per.

InformationsquelleAutor Jun Yuan | 2012-11-01

 

4 Replies
  1. 8

    Basato su Matthieu M. la risposta, ma più corta e senza macro:

    template<typename T, typename U> constexpr size_t offsetOf(U T::*member)
    {
        return (char*)&((T*)nullptr->*member) - (char*)nullptr;
    }

    E si chiama come questo:

    struct X { int a, b, c, d; }
    
    std::cout << "offset of c in X == " << offsetOf(&X::c);

    Edit:

    Jason Riso è corretto. Questo non produce una reale espressione costante in C++11. Non sembra possibile, date le restrizioni in http://en.cppreference.com/w/cpp/language/constant_expression — in particolare nessun puntatore differenza e reinterpret_castpuò essere in un costante espressione.

    • Solo per chiarirne il significato, U è il membro di tipo a e T è la struct tipo. Ho abborracciati coloro che per la prima volta.
    • Basso: abbiamo davvero bisogno - (char*)nullptr parte? Non siamo solo sottraendo zero? Credo che siamo in grado di renderlo più corto 🙂
    • Che sembra funzionare su gcc, ma non llvm: errore: impossibile inizializzare il ritorno di oggetti di tipo ‘size_t’ (aka ‘unsigned int’) con un rvalue di tipo ‘char *’
    • Se infatti, io uso il sopra per inizializzare un altro non compilare con clang/llvm.
    • Questo provoca comportamenti indefiniti per la risoluzione del riferimento puntatore nullo, e anche sottraendo i puntatori quando entrambi i puntatori non puntano allo stesso oggetto
  2. 4

    Bene… in C++11, è effettivamente in grado di calcolare questi scostamenti destra con regolare C++ strutture (vale a dire, senza delegare a un particolare compilatore intrinseca).

    In azione a liveworkspace:

    template <typename T, typename U>
    constexpr int func(T const& t, U T::* a) {
         return (char const*)&t - (char const*)&(t.*a);
    }

    Tuttavia questo si basa su t un riferimento ad un constexpr esempio qui, che potrebbero non essere applicabili a tutte le classi. Non vietare T da avere un virtual metodo, però, né il costruttore, purché sia un constexpr costruttore.

    Ancora, questo è piuttosto un ostacolo. In unevaluated contesto di cui si può effettivamente utilizzare std::declval<T>() per simulare la presenza di un oggetto; pur avendo nessuno. Questo non pone particolari esigenze di costruttore di un oggetto, quindi. D’altra parte, i valori che si possono estrarre da tale contesto sono pochi… e si pongono problemi di compilatori, troppo… Beh, facciamo finta!

    In azione a liveworkspace:

    template <typename T, typename U>
    constexpr size_t offsetof_impl(T const* t, U T::* a) {
        return (char const*)t - (char const*)&(t->*a) >= 0 ?
               (char const*)t - (char const*)&(t->*a)      :
               (char const*)&(t->*a) - (char const*)t;
    }
    
    #define offsetof(Type_, Attr_)                          \
        offsetof_impl((Type_ const*)nullptr, &Type_::Attr_)

    L’unico problema che prevedo è con virtual eredità, a causa della sua runtime posizionamento dell’oggetto di base. Sarei felice di essere presentato con altri difetti, se ci sono.

    • vedere anche il built-in macro offsetof
    • href=”http://en.cppreference.com/w/cpp/types/offsetof” >Attenzione => Se non è un tipo di layout standard tipo o se l’utente è un membro statico o una funzione membro, quindi il comportamento è indefinito di utilizzare il offsetof macro, come specificato dallo standard C non definito. In altre parole, il offsetof macro di C funziona solo con C-come tipi.
    • Perché stai usando T const* e char const* invece di T* e char*?
    • per rendere esplicito che la funzione non modifica il suo ingresso (non attraverso la macro, ma può essere utilizzato senza).
    • Appena morso da nuovo compilatore qui 🙂 credo che il intentioin delle norme di persone è quello di rendere gli spostamenti di classi di base all’interno di una classe non è costante, ma se un campo è in una classe di base, anche se la classe base non virtuale non funziona. Posso capire perché la base virtuale classi dovrebbero essere in tutto il luogo, ma per semplici basi ? Ho avuto un vecchio modello in cui si sarebbe messo in una classe di base, il “nodo” per una lista ( un invadente nodo sull’oggetto ) E l’elenco generico di codice necessaria solo l’offset e il nodo. Purtroppo non posso farlo 😐
  3. 2

    No, non in generale.

    La macro offsetof esiste per CONTENITORE (plain old dati) strutture, e può essere esteso leggermente con il C++0x di layout standard struct (o altri simili lieve estensioni). Così, per quelli limitati casi, si dispone di una soluzione.

    C++ offre un sacco di libertà di compilatore scrittori. Non so di qualsiasi clausola che impediva ad alcune classi con la variabile offset per i membri della classe, tuttavia, non sono sicuro del perché un compilatore sarebbe farlo. 😉

    Ora, un approccio per mantenere il codice conforme agli standard, ma che hanno ancora offset, sarebbe per attaccare i vostri dati in un CONTENITORE (o C++0x estensione) sub-struct, su cui offsetof lavoro, poi lavorare su quella sub-struct anziché dell’intera classe. Oppure si potrebbe cedere la conformità agli standard. L’offset della tua struttura all’interno della vostra classe non essere conosciuto, ma l’offset del membro all’interno della struct sarebbe.

    Una domanda importante da porsi è “perché voglio questo, e ho davvero una buona ragione”?

    • Potrebbe non essere una buona ragione, ma alcune interazioni di database richiedono costanti in fase di compilazione offset in strutture di dati per costruire gli indici. È possibile prefissare, ma lasciare che il compilatore calcola loro, invece in grado di evitare alcuni errori.
    • Che è un caso nemico plain old dati: mettere un non-standard di layout di classe in un database, in modo che sarà una cattiva idea.
  4. 0

    Nel 1996, il libro “all’Interno del C++ Object Model”, scritto da Stanley B. Lippman, originale in C++ designer, fa riferimento al Puntatore-a-Membro Funzioni nel Capitolo 4.4

    il valore restituito dal prendere l’indirizzo di non static membro dei dati è il valore di byte, il membro posizione nel layout della classe (+1). Si può pensare ad esso come un incompleto valore. Esso deve essere associato l’indirizzo di un oggetto di classe prima di una istanza di stati può essere letta.

    Mentre io vagamente ricordo che +1 da qualche parte, in qualche vita precedente, non ho mai visto o ha fatto uso di questa sintassi prima.

    class t
    {
    public:
        int i;
        int j;
    };
    int (t::*pmf)() = &t::i;

    Almeno secondo la descrizione, questo sembra essere un modo fantastico per ottenere il “quasi” l’offset.

    Ma non sembra funzionare più, almeno nel GCC. Ho un

    Cannot initialize a variable of type 'int (t::*) with an rvalue of type "int t:: *'

    Qualcuno ha qualche storia dietro quello che sta succedendo qui? È qualcosa di simile a questo è ancora possibile?

    Problema con il web — obsoleti con i libri non muoiono mai…

    • int (t::*pmf)() è che un puntatore a funzione membro?

Lascia un commento