2009-12-10 146 views
10

當然,我知道最好的答案是「不要編寫自己的跨平臺代碼,有人已經完成了你需要的東西」,但是我將其作爲一種愛好/學習鍛鍊而不是以任何有償能力。基本上,我正在用C++編寫一個小型的控制檯應用程序,並且我想讓它跨平臺,處理諸如文件,套接字和線程之類的東西。 OOP似乎是一種很好的方式來處理這個問題,但我還沒有真正找到一種編寫共享相同接口跨平臺的類的好方法。C++中的跨平臺OOP

簡單的方法是隻規劃一些元接口,在整個程序的其餘部分使用它,然後根據平臺編譯同一個類,並使用不同的文件,但是我覺得應該有更好的更優雅的方式;至少,不會混淆IntelliSense及其同類的東西會很好。

我已經採取了看看一些在wxWidgets的源較小的類,並且採用了可使用專用構件保持數據的類的方法,如

class Foo 
{ 
    public: 
     Foo(); 

     void Bar(); 
    private: 
     FooData data; 
}; 

然後,您可以編譯這個通過簡單地根據平臺選擇不同的實現文件。這種方法對我來說似乎很笨拙。

我考慮的另一種方法是編寫一個接口,並根據平臺交換從該接口繼承的類。事情是這樣的:

class Foo 
{ 
    public: 
     virtual ~Foo() {}; 
     virtual void Bar() = 0; 
}; 

class Win32Foo 
{ 
    public: 
     Win32Foo(); 
     ~Win32Foo(); 
     void Bar(); 
}; 

當然這種螺絲了實際的實例,因爲你不知道要創建的對象,它的實現,但可以通過周圍使用功能

Foo* CreateFoo(); 
進行加工的

並根據您運行的平臺改變函數的實現。我也不是這個巨大的粉絲,因爲它仍然看起來笨拙的代碼與一堆實例化方法(這也會與創建非跨平臺對象的方法不一致)。

這兩種方法哪種更好?有沒有更好的辦法?

編輯:爲了澄清,我的問題不是「你如何編寫跨平臺的C++?」相反,它是「什麼是用C++中的類抽象出跨平臺代碼的最好方法,同時儘可能保留類型系統的好處?」

回答

10

定義你的界面,它轉發到detail電話:

#include "detail/foo.hpp" 

struct foo 
{ 
    void some_thing(void) 
    { 
     detail::some_thing(); 
    } 
} 

其中「細節/ foo.hpp」是是這樣的:

namespace detail 
{ 
    void some_thing(void); 
} 

你會那麼detail/win32/foo.cppdetail/posix/foo.cpp實現這一點,而這取決於你的平臺的編譯,編譯一個或其他。

通用接口只轉發到特定的實現,實現通話。這與提升效果相似。你會想看看推動獲得完整的細節。

+1

這是個好主意,我一定會看看Boost在那裏做什麼。 雖然我很好奇,這種模式是否允許類的狀態因實現而異?例如,Posix與Win32文件句柄具有不同的類型 - 如果我正在編寫文件包裝器,則狀態結構/類將不得不不同。 – ShZ 2009-12-10 23:34:04

+2

你會創建一個'file_handle'類,並且通常把句柄填入'void *'。實現可以將其轉換回它所需要的。這是由標準保證安全。如果你動態加載實現,這個方法也可以很好地工作,因爲你只需從庫中加載'some_function_pointer'來替換'detail :: whatever'。 – GManNickG 2009-12-10 23:43:08

+1

+1:總是委託給構建系統什麼是構建時間特定的 – 2009-12-11 11:41:10

15

要真正實現需要OS支持的跨平臺功能(如套接字),您將必須編寫代碼,在某些平臺上,不會編譯。這意味着您的面向對象設計需要輔以預處理器指令。

不過,既然您必須使用預處理器,那麼從socket基類繼承而來的win32socket(例如)是否甚至是必要的或有用的問題是值得懷疑的。當不同的函數在運行時被多態選擇時,OO通常很有用。但跨平臺功能通常更多是編譯時問題。 (例如,如果該函數甚至不能在Linux上編譯,那麼在多態調用win32socket::connect時沒有用處)因此,最好簡單地創建一個socket類,該類根據使用預處理器指令的平臺以不同方式實現。

+9

這不是真的有必要用垃圾預處理程序條件代碼 - 只是把特定於平臺的代碼放到獨立的文件,並在生成文件做選擇/構建腳本。 – 2009-12-10 22:06:08

+1

正確;根據平臺的不同,預處理器指令可以像包含不同的頭文件一樣簡單。 – 2009-12-10 22:08:52

+0

絕對 - 我的問題更多的是關於如何創建通用套接字類的不同實現。對那裏的歧義抱歉。 – ShZ 2009-12-10 23:29:35

1

第二種方法是更好,因爲它可以讓你創建將只存在於某個平臺的成員函數。然後,您可以在獨立於平臺的代碼中使用抽象類,並在特定於平臺的代碼中使用具體類。

實施例:

class Foo 
{ 
    public: 
     virtual ~Foo() {}; 
     virtual void Bar() = 0; 
}; 
class Win32Foo : public Foo 
{ 
    public: 
     void Bar(); 
     void CloseWindow(); // Win32-specific function 
}; 

class Abc 
{ 
    public: 
     virtual ~Abc() {}; 
     virtual void Xyz() = 0; 
}; 
class Win32Abc : public Abc 
{ 
    public: 
     void Xyz() 
     { 
      // do something 
      // and Close the window 
      Win32Foo foo; 
      foo.CloseWindow(); 
     } 
}; 
2

理想情況下你將集中在可能的最低程度的差異。
因此,您的頂級代碼調用Foo()並且只有Foo()需要在內部關心它調用的操作系統。

ps。看看「升壓」它包含了很多東西,以處理跨平臺的網絡文件系統等

1

委託作爲你的第一個例子或GMAN的作品相當不錯,尤其是針對特定平臺的部分將需要很多特定於平臺的成員(例如的,只有在一個平臺上存在的類型的成員)。下行是你必須保持兩個階級。

如果類將會變得更簡單,沒有大量特定於平臺的成員,那麼您可以使用一個通用的類聲明,並在兩個不同的.cc文件中編寫兩個實現(另外還可以使用第三個.cc來獨立於平臺方法實現)。對於少數平臺特定的成員或特定於平臺的類型的成員,頭文件中可能需要一些#ifdefs。這種方式更多的是黑客攻擊,但可以更容易開始。如果失控,您可以隨時切換到代表。

4

你不需要去OOP,以跨平臺。跨平臺更多的是架構和設計範例。

我建議從標準的代碼隔離所有特定於平臺的代碼,如插座GUI。在不同的平臺上比較這些,並編寫一個通用的圖層或包裝。在你的代碼中使用這個圖層。創建庫函數以實現通用層的平臺特定實現。

平臺特定的功能應該是在單獨的庫或文件。您的構建過程應該爲特定平臺使用正確的庫。不應該改變你的「標準」代碼。

1

正如其他人所指出的那樣,繼承是這項工作的錯誤工具。在您的案例中使用具體類型的決定是在構建時進行的,並且經典多態性使得可以在運行時作出決定(即,由於用戶的行爲)。

2

您可以使用模板特化爲不同平臺分離代碼。

enum platform {mac, lin, w32}; 

template<platform p = mac> 
struct MyClass 
{ 
// Platform dependent stuff for whatever platform you want to build for default here... 
}; 

template<> 
struct MyClass<lin> 
{ 
// Platform dependent stuff for Linux... 
}; 

template<> 
struct MyClass<w32> 
{ 
// Platform dependent stuff for Win32... 
}; 

int main (void) 
{ 
MyClass<> formac; 
MyClass<lin> forlin 
MyClass<w32> forw32; 
return 0; 
} 
+0

這裏也出現了這個「成語」:http://altdevblog.com/2011/06/27/platform-abstraction-with-cpp-templates / – Julien 2016-06-27 12:53:15