這裏的工作原理是什麼,我試圖做的:如何實現在C++中的適配器框架,在Linux和Windows
我正在開發一個支持插件的跨平臺IDE(Linux和Windows)。我需要使用類似於Eclipse提供的適配器框架來支持可擴展性。見here更多細節,但基本上我需要以下條件:
讓Adaptee
和Adapted
是已經存在的,而我們是不允許以任何方式改變完全無關的類。我想創建一個AdapterManager
類有一個方法
template <class Adaptee, class Adapted> Adapted* adapt(Adaptee* object);
這將創造給予Adaptee
實例的Adapted
一個實例。如何創建實例取決於必須使用AdapterManager
註冊的適配器功能。每個新插件都應該能夠爲任意類型提供適配器功能。
下面是我對一個可能的解決方案的想法,爲什麼它不工作:
C++ 11的RTTI功能和
type_info
類提供hash_code()
方法,它返回在每個類型的唯一整數程序。請參閱here。因此AdapterManager
可能只包含一個映射,給定Adaptee和Adapter類的散列碼返回一個指向適配器函數的函數指針。這使得adapt()
函數的實現以上簡單:template <class Adaptee, class Adapted> Adapted* AdapterManager::adapt(Adaptee* object) { AdapterMapKey mk(typeid(Adapted).hash_code(), typeid(Adaptee).hash_code()); AdapterFunction af = adapterMap.get(mk); if (!af) return nullptr; return (Adapted*) af(object); }
任何插件可以很容易地通過簡單地插入一個附加功能到地圖延伸的框架。還要注意,任何插件都可以嘗試將任何類適配到任何其他類,並且如果存在相應的適配器函數(使用
AdapterManager
註冊),則無論是誰註冊它都會成功。- 此問題與模板和插件(共享對象/ DLL)的組合有關。由於兩個插件可以使用相同的參數實例化模板類,因此這可能會導致兩個單獨的相應結構實例和潛在的不同
hash_code()
結果,這將打破上述機制。從一個插件註冊的適配器功能可能並不總是在另一個插件中運行。 - 在Linux中,根據this(點4.2),在一些條件下,動態鏈接器似乎能夠處理不同共享庫中多個類型的聲明。然而真正的問題是在Windows中,似乎每個DLL都會得到它自己版本的模板實例化,而不管它是否也在其他加載的DLL或主要可執行文件中定義。與在Linux中使用的動態鏈接器相比,動態鏈接器看起來非常不靈活。
- 我已經考慮過使用顯式模板實例化,這似乎減少了這個問題,但仍然沒有解決它,因爲兩個不同的插件可能仍然以相同的方式實例化相同的模板。
問題:
- 有誰知道的一種方式在Windows中實現這一目標?如果你被允許修改現有的類,這會有幫助嗎?
- 您是否知道另一種在C++中實現這種功能的方法,同時仍然保留了所有期望的屬性:對現有類沒有改變,可以使用模板,支持插件並且是跨平臺的?
更新1:
本項目採用Qt框架的很多東西,包括插件的基礎設施。 Qt真的有助於跨平臺開發。如果你知道Qt特定的解決方案,那也是受歡迎的。
更新2:
N.M.的評論使我意識到,我只知道在理論上的問題,並沒有實際測試它。所以,我沒有使用下面的定義在Windows和Linux的一些測試:
template <class T>
class TypeIdTest {
public:
virtual ~TypeIdTest() {};
static int data;
};
template <class T> int TypeIdTest<T>::data;
該類在兩個不同的共享庫/有T = INT的DLL實例化。這兩個庫都在運行時顯式加載。以下是我發現:
在的Linux一切都只是工作:
- 這兩個實例中使用的相同虛函數表。
typeid
返回的對象位於同一地址。- 即使是靜態數據成員也是一樣的。
- 因此,模板在多個動態加載的共享庫中實例化的事實完全沒有區別。鏈接器似乎只是簡單地使用第一個加載的實例並忽略其餘部分。
在的Windows兩個實例都'有點'不同:
- 的
typeid
的不同實例返回不同的地點type_info
對象。然而,這些物體在使用==
進行測試時是相同的。相應的哈希碼也相同。它看起來像在Windows上類型之間的平等是使用類型的名稱建立的 - 這是有道理的。到現在爲止還挺好。 - 但是vtables這兩個實例是不同的。我不確定這是多少問題。在我的測試中,我能夠使用
dynamic_cast
將TypeIdTest
的一個實例向下轉錄到跨共享庫邊界的派生類型。 - 還有一個問題是,每個實例都使用自己的靜態字段副本
data
。這可能會導致很多問題,並且基本上禁止模板類中的靜態字段。
總體而言,似乎連在Windows事情並不像我想象的那樣糟糕,但我仍然不願意使用這種方法,因爲模板實例仍然使用不同的虛函數表和靜態存儲。有誰知道如何避免這個問題?我沒有找到任何解決方案。
我想你在編譯時試圖解決運行時問題。我認爲在這種情況下,繼承和虛擬函數比模板更直接,更容易出錯(因爲正如你所指出的那樣,你正在嘗試做的事情有很多魔法)。 – 2012-02-14 10:17:48
我認爲這個問題的實質是沒有辦法讓我的手掌握一個獨特的類型標識符(即使在運行時),這個標識符可以跨插件邊界使用。至少在Windows中似乎是這種情況。我不確定繼承和虛函數是否可行,因爲每個插件都可以貢獻新的類,這些類也應該是任意適應的。你有一個想法,它可以做什麼? – 2012-02-14 10:29:01
您不能在編譯時從插件調整arbirtrary類型,因爲您需要所述插件的頭文件。這打破了插件的目的,你可以同時編譯所有的東西。 很明顯,你不能在運行時使用C++中的arbirtrary類型。另一方面,您可以調整具有相同基類的任意類型。 如果這還不夠,那麼每個插件都必須提供一個註冊函數,該函數可以在程序開始時被調用,該註冊函數將進行必要的註冊所有適應類型及其各自的適配函數。 – 2012-02-14 10:36:25