2015-05-13 14 views
10

Sono in una situazione in cui ho un ciclo di dipendenza circolare tra le definizioni di due classi, dove (per quanto posso dire) entrambe le classi hanno bisogno che l'altro tipo sia un tipo completo per definirle correttamente.Praticamente sicuro di assumere sizeof (std :: unordered_map <std :: string, T>) è lo stesso per tutti i T?

In termini semplificati, quello che mi serve versione semplificata di ciò che accade:

struct Map; 

struct Node { 
    // some interface... 
private: 
    // this cannot be done because Map is an incomplete type 
    char buffer[sizeof(Map)]; 
    // plus other stuff... 
    void* dummy; 
}; 

struct Map { 
    // some interface... 
private: 
    // this is Map's only member 
    std::unordered_map<std::string, Node> map_; 
}; 

La situazione è in realtà più complicata di quanto sopra, poiché Node è in realtà sarà di tipo variante (simile a boost::variant) che utilizza il posizionamento nuovo per costruire esplicitamente uno di più tipi di oggetti in un buffer preallocato (e con allineamento corretto, che sto ignorando in questa semplificazione): il buffer non è quindi esattamente sizeof(Map) ma piuttosto una costante calcolata che dipende da sizeof(Map).

Il problema, ovviamente, è che sizeof(Map) non è disponibile quando Map viene inoltrato solo in avanti. Inoltre, se cambio l'ordine delle dichiarazioni da inoltrare prima dichiarare Node, la compilazione di Map non riesce, poiché std::unordered_map<std::string, Node> non può essere istanziato quando Node è un tipo incompleto, almeno con il mio GCC 4.8.2 su Ubuntu. (Lo so che dipende dalla libstdC++ versione più rispetto alla versione GCC, ma non so come trovare facilmente quello ...)

In alternativa, sto considerando la seguente soluzione:

struct Node { 
    // some interface... 
private: 
    // doing this instead of depending on sizeof(Map) 
    char buffer[sizeof(std::unordered_map<std::string, void*>)]; 
    // other stuff... 
    void* dummy; 
}; 

struct Map { 
    // some interface... 
private: 
    // this is Map's only member 
    std::unordered_map<std::string, Node> map_; 
}; 

// and asserting this after the fact to make sure buffer is large enough 
static_assert (sizeof(Map) <= sizeof(std::unordered_map<std::string, void*>), 
    "Map is unexpectedly too large"); 

Questo fondamentalmente si basa sul presupposto che std::unordered_map<std::string, T> abbia la stessa dimensione per tutti i T, il che sembra essere valido per i miei test con GCC.

La mia domanda è dunque triplice:

  • C'è qualcosa nello standard C++ che richiede che questa ipotesi di tenere vero? (Sto assumendo di no, ma se c'è sarei piacevolmente sorpreso ...)

  • In caso contrario, è praticamente lecito ritenere che vale per tutte le implementazioni ragionevoli in ogni caso, e che l'affermazione statica nella mia versione rivista non sparerà mai?

  • Infine, c'è una soluzione migliore a questo problema a cui non ho pensato? Sono sicuro che è possibile che ci sia qualcosa di ovvio che posso fare, invece, che non ho pensato, ma purtroppo non posso pensare a niente ...

+0

I commenti non sono per discussioni estese; questa conversazione è stata [spostata in chat] (http://chat.stackoverflow.com/rooms/77815/discussion-on-question-by-trantorian-practicor-safe-to-assume-sizeofstdunor). – Taryn

risposta

2

Basta andare avanti e assumere. Quindi al static_assert alla costruzione hai ragione.

ci sono soluzioni più elaborate, come per capire come aumentare le strutture di dati ricorsive lavoro e applicando la tecnica qui (che potrebbe richiedere per iscritto la propria mappa), o semplicemente utilizzando un contenitore boost:: che supporta strutture di dati incompleti.

+0

Per ora ho accettato questo dato che tu sei l'unica persona che non mi dice di usare un'ulteriore indirezione (che non è in realtà una soluzione al problema, semplicemente evitandola). Sarei curioso di poterti elaborare su strutture dati ricorsive, però - immagino che sia un modo per sfruttare il binding di nome template differito per consentire strutture con tipi mutualmente dipendenti? – Trantorian

+0

@tran come funziona opuscoli ricorsivi. Alcuni tag che il modello conosce si riferiscono a se stesso. – Yakk

+0

ok, grazie, penso di aver capito l'idea.forse qualcosa del genere aiuterà - terrò la versione 'static_assert' per ora mentre penso a qualcosa del genere come alternativa in background – Trantorian

1

1) No

2) I Non sono sicuro

3) È inoltre possibile utilizzare il modello di progettazione Metodo di produzione. La tua fabbrica restituirà l'oggetto in base alla variante Map (MODIFICA: voglio dire che utilizzerai l'istanza Variante della mappa come parametro e l'implementazione del metodo Factory utilizzerà tali informazioni per creare di conseguenza l'oggetto di ritorno) e può preallocare il buffer per correggere la dimensione.

+0

Puoi chiarire cosa stai suggerendo con 3)? In particolare, non voglio un'ulteriore indiretta attraverso i puntatori di quella che ho nella versione rivista (che funziona quando passa 'static_assert'). 'Node' è effettivamente un'unione che contiene uno di una varietà di tipi senza indirezione indiretta, e l'elenco di possibili tipi include' Map', 'std :: string' e' bool'. – Trantorian

+0

Una delle istanze di 'Node' restituirà un tipo durante la vita o varierà? E conosci i modelli di progettazione? –

+0

Avrò un solo tipo alla volta, ma può cambiare. Se il tipo viene modificato, il 'Node' chiamerà esplicitamente il distruttore di tipo corretto sui contenuti correnti e sul posizionamento, nuovo un oggetto del nuovo tipo, aggiornando un tag di tipo nel processo.Ho familiarità con gli schemi di progettazione ma non vedo cosa intendi - in particolare, sembra che tu stia suggerendo che la factory restituirà qualche tipo di puntatore generico, come 'void *' per evitare 'Node' a seconda delle dimensioni di 'Map'; questo è esattamente ciò che non voglio perché l'extraindirizzamento aggiuntivo si sommerà rapidamente poiché si tratta di una struttura ricorsiva. – Trantorian

3

1) Nessun

2) contenitori STL non può essere istanziata con un tipo incompleto.Tuttavia, a quanto pare alcuni compilatori lo consentono. Non permettere ciò non è stata una decisione banale e in molti casi la tua ipotesi sarà vera. L'articolo This potrebbe interessarti. Dato il fatto che secondo lo standard questo problema non è risolvibile senza aggiungere uno strato di riferimento indiretto e non si vuole farlo. Devo solo dare un testa a testa che in realtà non stai facendo le cose secondo lo standard.

Detto questo, penso che la soluzione sia la migliore con i contenitori STL. E l'asserzione statica avvertirà effettivamente quando la dimensione supera effettivamente la dimensione prevista.

3) Sì, con l'aggiunta di un ulteriore livello di indirezione, la mia soluzione sarebbe il seguente:

Il problema che hai è che la dimensione di un oggetto, dipende dalle dimensioni dei suoi array. Diciamo che avere un oggetto A e B e l'oggetto:

struct A 
{ 
    char sizeof[B] 
} 

struct B 
{ 
    char sizeof[A] 
} 

oggetto A crescerà, al fine di caratteri casa per le dimensioni di B. Ma poi a sua volta l'oggetto B dovrà crescere. Immagino che tu possa vedere dove sta andando. So che questo è il tuo problema esatto, ma penso che i principi di base siano abbastanza simili.

In questo caso particolare voglio risolverlo cambiando la linea

char buffer[sizeof(Map)]; 

essere solo un puntatore:

char* buffer 

E dinamicamente allocare memoria dopo l'inizializzazione. Semina il file cpp sarebbe simile a questa:

//node.cpp 
//untested code 
node::node() 
{ 
    buffer = malloc(sizeof(map)); 
} 

node::~node() 
{ 
    free buffer; 
} 
+0

No, 'Map' contiene' std :: unordered_map ', non' Node' direttamente, e il primo non cresce effettivamente con la dimensione di 'Nodo', quindi questo caso non è come il caso che stai dicendo. In pratica, 'std :: unordered_map ' in realtà non cambia la dimensione in quanto 'Node' cambia, perché non contiene direttamente un' Node'; qualsiasi implementazione per la quale la dimensione cambia in base al parametro 'value_type' deve fare qualche strana ottimizzazione che sto cercando di escludere _praticamente in pratica. – Trantorian

+0

Inoltre, non voglio allocare dinamicamente la memoria come stai dicendo, perché aggiunge un ulteriore livello di riferimento indiretto. Vedi la discussione nei commenti sulla domanda per ulteriori spiegazioni. – Trantorian

+0

@Trantorian Ufficialmente qualsiasi dichiarazione di specializzazione fatta con un tipo, richiede un tipo completo. Potrebbe essere vero che 'unordered_map' in realtà non cambia le dimensioni. Ma preferirei non andarci. Significa che devi implementare entrambe le classi senza un tipo completo. Questo è semplicemente un requisito di C++, non è possibile dichiarare due classi dipendenti dal tipo completo di eachothers. Temo che l'unico modo per aggirare questo per quanto ne so, è in effetti l'aggiunta di un altro livello di riferimento indiretto. – laurisvr

1

1) Probabilmente non

2) Dal momento che sembra che sia totalmente dipendente implementazione, che è un grande rischio come questo può letteralmente rompere in qualsiasi aggiornamento compilatore .

3) È possibile utilizzare boost::unordered_map che accetta tipi incompleti e quindi risolverà il problema.

Problemi correlati