2015-10-23 15 views
7

Ho esaminato le seguenti domande correlate e nessuno di esse sembra risolvere il mio problema esatto: one, two, three.Questo uso di reinterpret_cast su membri struct qualificati in modo diverso è sicuro?

Sto scrivendo una collezione di cui gli elementi (coppie chiave-valore) sono memorizzate insieme ad alcune informazioni di contabilità:

struct Element { 
    Key key; 
    Value value; 
    int flags; 
}; 

std::vector<Element> elements; 

(Per semplicità, supponiamo che sia Key e Value sono i tipi standard di layout . la raccolta non verrà utilizzato con qualsiasi altro tipo comunque.)

al fine di sostenere l'accesso iteratore-based, ho iteratori che sovrascrivono operator-> e operator* per restituire all'utente un puntatore e un riferimento, rispettivamente, scritte , t o la coppia chiave-valore. Tuttavia, a causa della natura della raccolta, l'utente non è mai autorizzato a modificare la chiave restituita. Per questo motivo, ho dichiarato una struttura KeyValuePair:

struct KeyValuePair { 
    const Key key; 
    Value value; 
}; 

E ho implementato operator-> sull'iteratore come questo:

struct iterator { 
    size_t index; 

    KeyValuePair *operator->() { 
     return reinterpret_cast<KeyValuePair *>(&elements[index]); 
    } 
}; 

La mia domanda è: è questo uso della reinterpret_cast ben definito o invoca un comportamento indefinito? Ho cercato di interpretare parti rilevanti della norma ed esaminato le risposte alle domande relative a problemi simili, tuttavia, non sono riuscito a trarre una conclusione definitiva da loro, perché ...:

  • i due tipi struct condividono alcuni membri di dati iniziali (vale a dire, key e value) che differiscono solo in const -qualificazione;
  • lo standard non dice esplicitamente che T e cv T sono compatibili con il layout, ma non indica il contrario; inoltre, impone loro di avere gli stessi requisiti di rappresentazione e allineamento;
  • Due tipi di classi di layout standard condividono una sequenza iniziale comune se il primo, tuttavia, molti membri hanno tipi compatibili con il layout;
  • per i tipi di unione contenenti membri di tipo di classe che condividono una sequenza iniziale comune, è consentito esaminare i membri di tale sequenza iniziale utilizzando uno dei membri del sindacato (9.2p18). - non esiste alcuna garanzia esplicita simile su reinterpret_cast ed i puntatori alle strutture condividono una sequenza iniziale comune. - è, tuttavia, garantito che un puntatore a struttura punti al suo membro iniziale (9.2p19).

Usando solo queste informazioni, ho trovato l'impossibilità di dedurre se le Element e KeyValuePair struct condividono una sequenza iniziale comune, o di avere qualcosa di diverso in comune che giustifichi la mia reinterpret_cast.

Per inciso, se pensate che l'utilizzo di reinterpret_cast per questo scopo sia inappropriato, e io sto davvero affrontando un problema XY e quindi dovrei semplicemente fare qualcos'altro per raggiungere il mio obiettivo, fatemelo sapere.

+0

Stai chiedendo un giudizio [tag: language-lawyer]? Funziona davvero? 'reinterpret_cast' è quasi sempre l'approccio sbagliato, non dovrebbe un' const_cast' funzionare bene per quello che stai rivendicando? –

+0

@ πάνταῥεῖ sì, sto chiedendo un giudizio di un avvocato linguistico. ** Non posso dire se effettivamente funziona ** o semplicemente "sembra funzionare" poiché non so se invoca un comportamento indefinito. Non so se 'const_cast' dovrebbe funzionare in questo caso, ma fammi provare - non è ancora chiaro, tuttavia, se la compilazione di' const_cast' significherebbe che questo è ben definito. –

+0

_ "tuttavia, se la compilazione const_cast significherebbe che questo è ben definito." _ Naturalmente, penso che sia lo scopo. L'uso di un 'reinterpret_cast' non può garantire nulla. –

risposta

7

La mia domanda è: è questo uso di reinterpret_cast ben definito, oppure invoca un comportamento non definito?

reinterpret_cast è l'approccio sbagliato qui, stai semplicemente violando il rigoroso aliasing. È un po 'perplesso che reinterpret_cast e union divergano qui, ma la formulazione è molto chiara su questo scenario.

Si potrebbe essere meglio semplicemente definendo un'unione così:

union elem_t { 
    Element e{}; KeyValuePair p; 
    /* special member functions defined if necessary */ 
}; 

... e l'utilizzo che come tipo di elemento del vettore. Si noti che cv-qualificazione viene ignorato quando si determina il layout-compatibilita - [basic.types]/11:

Due tipi CV1T1 e cv2T2 sono i tipi di layout-compatibili se T1 e T2 sono il stesso tipo, [...]

Quindi Element e KeyValuePair facciano realmente condividono una sequenza iniziale comune, e l'accesso ai corrispondenti componenti p, a condizione e è vivo, è ben definito.


Un altro approccio: Definire

struct KeyValuePair { 
    Key key; 
    mutable Value value; 
}; 

struct Element : KeyValuePair { 
    int flags; 
}; 

ora fornire un iteratore che avvolge semplicemente una const_iterator dal vettore e upcasts i riferimenti/puntatori di essere esposti. key non sarà modificabile, ma sarà value.

+1

Grazie! In realtà non sorprende sapere che 'reinterpret_cast' è semplicemente la cosa sbagliata da fare. –

+0

Non c'è niente di sbagliato in 'struct Element {struct KeyValuePair pair; int flags} '. – Persixty