2015-05-13 7 views
10

Ich bin in einer Situation, wo ich eine zirkuläre Abhängigkeitsschleife zwischen den Definitionen von zwei Klassen habe, wo (so weit ich sagen kann) beide Klassen den anderen Typ benötigen, um ein vollständiger Typ zu sein, um sie richtig zu definieren.Praktisch sicher anzunehmen sizeof (std :: unordered_map <std :: string, T>) ist für alle T gleich?

Vereinfacht ausgedrückt, was ich vereinfachte Version müssen, was los ist:

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

Die Situation ist tatsächlich komplizierter als die oben, da Node tatsächlich wird eine Variante Typ (ähnlich boost::variant sein) das verwendet Placement neu, um explizit einen von mehreren Arten von Objekten in einem vorher zugeordneten (und mit richtiger Ausrichtung, die ich in dieser Vereinfachung ignoriere) Puffer zu konstruieren: der Puffer ist daher nicht genau sizeof(Map), sondern eher eine bestimmte Konstante, die abhängig ist sizeof(Map). Das Problem besteht offensichtlich darin, dass sizeof(Map) nicht verfügbar ist, wenn Map nur vorwärts deklariert ist. Außerdem, wenn ich die Reihenfolge der Deklarationen zu Forward deklarieren Node zuerst, dann Kompilierung von Map schlägt fehl, wie std::unordered_map<std::string, Node> kann nicht instanziiert werden, wenn Node ist ein unvollständiger Typ, zumindest mit meinem GCC 4.8.2 auf Ubuntu. (Ich weiß, es hängt von dem libstdC++ Version mehr als die GCC-Version, aber ich weiß nicht so leicht, wie das finden ...)

Als Alternative Ich betrachte die folgende Abhilfe:

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

Dies beruht im Wesentlichen auf der Annahme, dass std::unordered_map<std::string, T> die gleiche Größe für alle T ist, die aus meinen Tests mit GCC wahr scheint. also dreifache

ist Meine Frage:

  • Gibt es etwas in der C++ Standard, der erfordert, dass diese Annahme wahr zu halten? (Ich gehe davon aus, nein, aber wenn es ist, würde ich angenehm überrascht sein ...)

  • Wenn nicht, ist es praktisch sicher davon ausgehen, es für alle vernünftigen Implementierungen gilt sowieso, und dass die statische Behauptung in meiner überarbeiteten Version wird nie feuern?

  • Schließlich gibt es eine bessere Problemumgehung für dieses Problem, an das ich nicht gedacht habe? Ich bin sicher, es ist möglich, es ist etwas offensichtlich, dass ich stattdessen tun kann, dass ich nicht gedacht haben, aber leider kann ich mich nichts ...

+0

Kommentare sind nicht für längere Diskussion; Diese Konversation wurde [in den Chat verschoben] (http://chat.stackoverflow.com/rooms/77815/discussion-on-question-by-trantorian-practically-safe-to-assume-sizeofstdunor). – Taryn

Antwort

2

Gehen Sie einfach vor und nehmen Sie an. Dann static_assert bei der Konstruktion, dass Sie Recht haben.

Es gibt raffiniertere Lösungen, wie herauszufinden, wie boost rekursive Datenstrukturen arbeiten und die Technik hier anwenden (was möglicherweise das Schreiben einer eigenen Karte erforderlich macht), oder einfach einen boost:: Container verwenden, der unvollständige Datenstrukturen unterstützt.

+0

Ich akzeptierte das für jetzt, da du die einzige Person bist, die mir nicht sagt, dass ich eine extra Indirection benutzen soll (was nicht wirklich eine Lösung für das Problem ist, nur um es zu vermeiden). Ich wäre neugierig, wenn Sie sich auf rekursive Datenstrukturen konzentrieren könnten, obwohl - ich vermute, es ist eine Möglichkeit, die verzögerte Template-Namensbindung auszunutzen, um Strukturen mit voneinander abhängigen Typen zu ermöglichen? – Trantorian

+0

@tran wie rekursiv optional funktioniert. Einige Tags, die die Vorlage kennt, verweisen auf sich selbst. – Yakk

+0

Okay, danke, ich denke, ich habe die Idee.vielleicht hilft so etwas - ich werde die 'static_assert' -Version für jetzt behalten und über so etwas als Alternative im Hintergrund nachdenken – Trantorian

1

1) Keine

2) I bin nicht sicher

3) Sie können auch Entwurfsmuster Factory-Methode verwenden. Ihre Fabrik wird Objekt basierend auf Map Variante zurückgeben (BEARBEITEN: Ich meine, Sie werden Map variant instance als Parameter verwenden und die Factory-Methodenimplementierung wird diese Information verwenden, um ein Rückgabeobjekt entsprechend zu erzeugen) und kann Puffer zur Größenzuordnung vorbelegen.

+0

Kannst du klarstellen, was du mit 3 vorschlägst? Insbesondere möchte ich keine zusätzliche Indirektion durch Zeiger, als das, was ich in der überarbeiteten Version habe (was funktioniert, wenn das 'static_assert' erfolgreich ist). 'Node' ist im Prinzip eine Union, die einen von vielen Typen ohne Indirection enthält, und die Liste der möglichen Typen beinhaltet' Map', 'std :: string' und' bool'. – Trantorian

+0

Eine Ihrer "Node" -Instanzen gibt einen Typ zur Laufzeit zurück oder wird variieren? Und sind Sie mit Designmustern vertraut? –

+0

Ich werde nur einen Typ gleichzeitig haben, aber es kann sich ändern. Wenn der Typ geändert wird, ruft der 'Node' explizit den Destruktor des richtigen Typs für den aktuellen Inhalt und das Placement-New ein Objekt des neuen Typs auf und aktualisiert dabei ein Typ-Tag.Ich bin mit Designmustern vertraut, sehe aber nicht, was Sie meinen - insbesondere scheint es, als würden Sie vorschlagen, dass die Factory einen generischen Zeigertyp zurückgibt, wie 'void *', um 'Node' zu ​​vermeiden, abhängig von der Größe von 'Karte'; Dies ist genau das, was ich nicht möchte, weil die zusätzliche Indirektion sich schnell summiert, da dies eine rekursive Struktur ist. – Trantorian

3

1) Keine

2) STL Behälter nicht mit einem unvollständigen Typ instanziiert werden.Offensichtlich erlauben einige Compiler dies jedoch. Dies nicht zuzulassen, war keine triviale Entscheidung und in vielen Fällen wird Ihre Annahme wahr sein. This Artikel könnte Sie interessieren. Angesichts der Tatsache, dass dieses Problem gemäß dem Standard nicht lösbar ist, ohne eine indirekte Ebene hinzuzufügen, und das wollen Sie nicht tun. Ich muss dir nur einen Hinweis geben, dass du in der Tat keine Dinge nach dem Standard machst.

Having said that, ich denke, dass Ihre Lösung die beste ist mit STL-Containern. Und die statische Behauptung wird tatsächlich warnen, wenn die Größe tatsächlich die erwartete Größe überschreitet.

3) Ja mit einer weiteren Schicht Indirektionsebene Zugabe wäre meine Lösung die folgende sein:

Das Problem, das Sie ist haben, dass die Größe eines Objekts auf der Größe seiner Arrays abhängt. Sagen Sie bitte ein Objekt A und Objekt und B haben:

struct A 
{ 
    char sizeof[B] 
} 

struct B 
{ 
    char sizeof[A] 
} 

Objekt A wird wachsen, um Haus Zeichen für die Größe von B. Aber dann wiederum Objekt B wird wachsen müssen. Ich denke, du kannst sehen, wohin das geht. Ich weiß, das ist dein genaues Problem, aber ich denke, die zugrunde liegenden Prinzipien sind ziemlich ähnlich.

In diesem speziellen Fall würde ich es lösen, indem die nur ein Zeiger auf seine

char buffer[sizeof(Map)]; 

Linie zu ändern:

char* buffer 

und dynamisch zuweisen Speicher nach der Initialisierung. Säen Sie Ihre CPP-Datei wie folgt aussehen würde:

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

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

Nein, 'Map' enthält' std :: unordered_map ', nicht' Node' direkt, und ersteres wächst nicht mit der Größe von 'Node', also ist dieser Fall nicht wie der Fall sagst du. In der Praxis ändert 'std :: unordered_map ' die Größe nicht, da sich 'Node' ändert, weil es nicht direkt einen' Node' enthält; Jede Implementierung, bei der sich die Größe basierend auf dem Parameter "value_type" ändert, muss eine seltsame Optimierung ausführen, die ich in der Praxis praktisch auszuschließen versuche. – Trantorian

+0

Außerdem möchte ich Speicher nicht dynamisch zuweisen, wie Sie sagen, weil es eine zusätzliche Ebene der Indirektion hinzufügt. Sehen Sie sich die Diskussion in den Kommentaren zu dieser Frage an, um weitere Erläuterungen zu erhalten. – Trantorian

+0

@Trantorian Offiziell jede Spezialisierungserklärung, die mit einem Typ gemacht wird, erfordert einen vollständigen Typ. Es kann sein, dass "unordered_map" tatsächlich die Größe nicht ändert. Aber ich würde trotzdem lieber nicht dorthin gehen. Das bedeutet, dass Sie eine Klasse ohne einen vollständigen Typ implementieren müssen. Dies ist einfach eine Voraussetzung von C++, Sie können nicht zwei Klassen abhängig vonothersotheren Typ angeben. Ich fürchte, der einzige Weg, um so weit zu kommen, wie ich weiß, ist in der Tat eine weitere Schicht der Indirektion. – laurisvr

1

1) Wahrscheinlich nicht

2) Da es aussieht wie es auf die Umsetzung völlig abhängig ist, das ist ein großes Risiko, da dies buchstäblich in jedem Compiler Update brechen .

3) Sie können boost::unordered_map verwenden, die unvollständige Typen akzeptieren und damit Ihr Problem lösen wird.

Verwandte Themen