2015-05-13 48 views
10

我處於兩個類的定義之間存在循環依賴循環的情況,其中(據我所知)兩個類都需要另一個類型爲完整類型才能正確定義它們。假設sizeof(std :: unordered_map <std :: string,T>)在所有T中都是相同的?

簡單的說,我需要的是怎麼回事的簡化版本:

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

其實比上述更復雜,因爲Node實際上將是一個變量類型(類似於boost::variant情況)使用placement new來顯式地構造預分配的多種對象中的一種(並且具有正確的對齊,我在此簡化中忽略)緩衝區:因此緩衝區不完全是sizeof(Map),而是一些計算出的常量,這取決於sizeof(Map)

顯然,問題是sizeof(Map)僅在Map只是前向聲明時纔可用。此外,如果我更改聲明的順序以首先轉發聲明Node,那麼編譯Map失敗,因爲std::unordered_map<std::string, Node>不能在Node是不完整類型時實例化,至少在我的Ubuntu上使用GCC 4.8.2。 (我知道這取決於的libstdC++版本比GCC版本的更多,但我不知道易如何找到...)

作爲替代方案,我考慮了以下解決方法:

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

這基本上依賴於假設std::unordered_map<std::string, T>是所有T的大小相同,這似乎從我使用GCC的測試中保持不變。

我的問題是這樣三個方面:

  • 是否有在C++標準的任何要求,這個假設是成立的? (我假設沒有,但如果有,我會驚喜...)

  • 如果不是,它是幾乎安全假設它適用於所有合理實現真正反正,那靜態斷言在我修改後的版本里永遠不會開火?

  • 最後,有沒有更好的解決這個問題,我沒有想到?我敢肯定,這可能有一些很明顯我可以做的,而不是說我沒有想到的,但不幸的是,我不能想什麼......

+0

評論不適用於擴展討論;這個對話已經[轉移到聊天](http://chat.stackoverflow.com/rooms/77815/discussion-on-question-by-trantorian-practically-safe-to-assume-sizeofstdunor)。 – Taryn

回答

2

只要繼續並假設。然後static_assert在施工你是對的。如果想知道如何提升遞歸數據結構的工作原理和應用這裏的技術(可能需要編寫自己的地圖),或者只使用支持不完整數據結構的容器,那麼就有更好的解決方案。

+0

我現在接受了這個,因爲你是唯一不告訴我使用額外間接尋址的人(這不是真正解決問題的方法,只是避免了)。但是,如果你可以詳細闡述遞歸數據結構,我會很好奇 - 我猜測它是利用延遲模板名稱綁定來允許具有相互依賴類型的結構的一種方式? – Trantorian

+0

@tran喜歡如何遞歸可選作品。模板知道的一些標籤是指自己。 – Yakk

+0

好的,謝謝,我想我明白了。也許這樣的事情會有所幫助 - 我現在要保留'static_assert'版本,並在後臺考慮類似的東西 – Trantorian

1

1)無

2)I '不確定

3)您也可以使用設計模式工廠方法。您的工廠將根據Map變體返回對象(編輯:我的意思是您將使用Map變體實例作爲參數,Factory方法實現將使用該信息來相應地創建返回對象),並且可以預先分配緩衝區以正確調整大小。

+0

你能否澄清你在3)中提出的建議?特別是,我不希望通過指針進行額外的間接指向,而不是我在修訂版本中(當'static_assert傳遞時)。 'Node'實際上是一個聯合體,它包含多種類型中的一種,沒有間接性,可能的類型列表包括'Map','std :: string'和'bool'。 – Trantorian

+0

您的一個'Node'實例會在生命週期返回一種類型,或者它會有所不同?你熟悉設計模式嗎? –

+0

我一次只有一種類型,但它可以改變。如果類型被改變,那麼'Node'將在當前的內容和位置上顯式地調用正確的類型析構函數 - 新的類型的對象,更新過程中的類型標記。我對設計模式很熟悉,但沒有看到你的意思 - 特別是,你似乎建議工廠將返回一些泛型指針類型,比如'void *'以避免'節點',這取決於大小'Map';這正是我不想要的,因爲這是一個遞歸結構,額外的間接會快速加起來。 – Trantorian

3

1)否

2)STL容器不能用不完整的類型實例化。但是,顯然有些編譯器確實允許這樣做。不允許這不是一個微不足道的決定,在許多情況下,你的假設確實會成立。 This文章可能會令你感興趣。鑑於根據標準,如果不添加一個間接層並且你不想這樣做,這個問題是不能解決的。我只是要提出一個問題,你確實沒有按照標準做事。

話雖如此,我認爲你的解決方案是最好的使用stl容器。而靜態斷言確實會警告,當尺寸確實超過預期的大小。

3)是與間接增加另一層,我的解決辦法是以下幾點:

你的問題是,物體的大小,取決於它的數組的大小。假設你有一個對象和客體B:

struct A 
{ 
    char sizeof[B] 
} 

struct B 
{ 
    char sizeof[A] 
} 

對象A將會增長,爲了房子的char B的大小,但隨後又將對象B將有增長。我想你可以看到這是怎麼回事。我知道這是你確切的問題,但我認爲其基本原理非常相似。

char* buffer 

,並動態初始化後分配內存:

在這種特殊情況下我會通過改變

char buffer[sizeof(Map)]; 

號線只是一個指針解決。母豬您的CPP文件看起來是這樣的:

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

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

不,'Map'直接包含'std :: unordered_map ',而不是'Node',前者實際上並不隨節點的大小增長,所以這種情況不像你說的話。實際上,'std :: unordered_map '實際上並沒有像Node一樣改變大小,因爲它不直接包含'Node';任何基於'value_type'參數大小改變的實現都必須做一些奇怪的優化,我試圖排除在實踐中實際發生的事情。 – Trantorian

+0

另外,我不想動態分配內存,就像你說的那樣,因爲它增加了一個額外的間接層。有關更多解釋,請參閱關於該問題的評論中的討論。 – Trantorian

+0

@Trantorian正式使用類型進行的任何專業化聲明都需要完整的類型。 'unordered_map'實際上並沒有改變大小,這可能是真的。但我寧願不去那裏。這意味着你必須實現沒有完整類型的任何一個類。這只是C++的一個要求,您不能聲明兩個依賴於其他完整類型的類。據我所知,恐怕唯一的解決辦法就是增加另一層間接尋址。 – laurisvr

1

1)或許不會

2),因爲它看起來像它完全依賴於實現,這是一個很大的風險,因爲這可以在任何編譯器更新字面上打破。

3)您可以使用boost::unordered_map哪些將接受不完整的類型,從而解決您的問題。

相關問題