2009-12-10 11 views
10

もちろん、私は最良の答えは「あなた自身のクロスプラットフォームコードを書かないで、誰かがあなたが必要としていることをしている」ということですが、私はこれを趣味/学習としてやっていますどんな給料をもらっていない。基本的には、C++で小さなコンソールアプリケーションを作成しています。ファイル、ソケット、スレッドなどを扱うクロスプラットフォームにしたいと考えています。 OOPはこれを処理する素晴らしい方法のように思えますが、クロスプラットフォームを共有する同じインターフェイスを共有するクラスを書くための良いパターンは実際には見つかりませんでした。C++でのクロスプラットフォームOOP

簡単なアプローチは、いくつかのメタインターフェイスを計画し、それをプログラムの残りの部分で使用し、プラットフォームに応じて異なるファイルで同じクラスをコンパイルするだけですが、それはよりエレガントです。少なくとも、IntelliSenseとそのilkを混乱させないものが良いでしょう。

あなたはその後、これをコンパイルすることができます例えば

class Foo 
{ 
    public: 
     Foo(); 

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

、私はwxWidgetsのソース内の小さいクラスのいくつかを見て撮影した、と彼らはクラスのデータを保持するプライベートメンバを使用するアプローチを使用プラットフォームに応じて異なる実装ファイルを選択するだけです。このアプローチは、私にとってかなり厄介なようです。

私が考えてきたもう1つのアプローチは、インターフェイスを作成し、プラットフォームに応じてそのインターフェイスを継承するクラスを交換することです。このような何か:もちろん

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

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

ネジアップあなたがオブジェクトを作成するために、どの実装を知らないが、それは機能

Foo* CreateFoo(); 
を使用することによって回避することができますので、実際のインスタンス化のこの種

を実行し、実行しているプラ​​ットフォームに基づいて関数の実装を変更します。私はこれも大いに賛成ではありません。なぜなら、まだインスタンス化メソッドの束でコードが浪費されているように思えます(これは、非クロスプラットフォームオブジェクトを作成する方法とも矛盾します)。

これらの2つのアプローチのどちらが優れていますか?より良い方法がありますか?

編集:明確にするために、私の質問は「クロスプラットフォームのC++をどうやって書くのですか?むしろ、C++でクラスを使用してクロスプラットフォームコードを抽象化する最良の方法はありますが、可能な限り型システムから多くの利益を得ていますか?

答えて

10

detail通話に転送し、あなたのインタフェースを定義し、それはクロスプラットフォームネットワークファイルシステムを処理するために多くのものが含まれている「ブースト」を見てみましょう:

「詳細/ foo.hpp」である
#include "detail/foo.hpp" 

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

以下のようなもの:

namespace detail 
{ 
    void some_thing(void); 
} 

あなたはその後、detail/win32/foo.cppまたはdetail/posix/foo.cppでこれを実装し、あなたのためにコンパイルされ、プラットフォームに応じて、どちらか一方をコンパイルすると思います。

一般的なインターフェイスは、実装固有の実装への呼び出しを転送するだけです。これはどのようにブーストを行うのかと同じです。あなたは完全な詳細を得るためにブーストを見たいと思うでしょう。

+1

これはいい考えです。私はBoostが何をしているかを見ていきます。 私は不思議ですが、このパターンは実装間でクラスの状態を変えることができますか?たとえば、PosixとWin32のファイルハンドルは異なるタイプです。ファイルラッパーを作成していれば、状態構造体/クラスは異なる必要があります。 – ShZ

+2

あなたは 'file_handle'クラスを作成し、通常は' void * 'にハンドルを埋め込みます。実装は、必要なものに戻すことができます。これは標準によって安全に保証されています。このメソッドは、ライブラリーからロードされた 'detail :: whatever'を' some_function_pointer'に置き換えるだけで、実装を動的にロードする場合にもうまくいきます。 – GManNickG

+1

+1:常にビルドシステムに委譲する具体的なビルド時間はどれですか –

15

OSサポート(ソケットなど)が必要なクロスプラットフォーム機能を実際に実装するには、というコードを書く必要がありますが、特定のプラットフォームではがコンパイルされません。つまり、オブジェクト指向設計にプリプロセッサ命令を補足する必要があります。

しかし、とにかくプリプロセッサを使用する必要があるので、socketベースクラスから継承するwin32socket(たとえば)のようなものが必要であろうと有用であろうと疑問です。 OOは、異なる機能が実行時に多形的に選択される場合に一般に有用である。しかし、クロスプラットフォームの機能は、通常はコンパイル時の問題です。 (例えば、win32socket::connectを多態的に呼び出しても、その関数がLinux上でコンパイルされていない場合は使用できません!)したがって、プリプロセッサディレクティブを使用するプラットフォームに応じて異なる方法で実装されるsocketクラスを単純に作成する方がよいでしょう。

+9

それは本当に必要なごみのプリプロセッサの条件とあなたのコードではありません - ちょうどスクリプトを作成/別のファイルにプラットフォーム固有のコードを入れて、メイクファイルで選択を行います。 –

+1

右プリプロセッサディレクティブは、プラットフォームに応じて異なるヘッダーファイルを含むだけの簡単なものにすることができます。 –

+0

絶対に - 私の質問は、汎用ソケットクラスのさまざまな実装を作成する方法についてもっと詳しく説明しました。そこのあいまいさに対する謝罪。 – ShZ

1

2つ目の方法は、いずれかのプラットフォームのみに存在するメンバー関数を作成できるため、より優れています。次に、プラットフォーム独立コードで抽象クラスを使用し、プラットフォーム固有コードで具体クラスを使用できます。

例:

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()のみが呼び出すOSを内部的に気にする必要があります。

ps。など

1

最初の例やGManのように委任するのは、特にプラットフォーム固有の部分(たとえば、1つのプラットフォームにのみ存在する型のメンバー)が必要なプラットフォームの場合です。欠点は、2つのクラスを維持する必要があることです。

プラットフォーム固有のメンバーがたくさんなくても、クラスがよりシンプルになる場合は、共通のクラス宣言を使用して、2つの異なる.ccファイルに2つの実装を書き込むことができますメソッド実装)。いくつかのプラットフォーム固有のメンバー、またはプラットフォーム固有のタイプのメンバーについては、ヘッダーファイルにいくつかの#ifdefが必要な場合があります。この方法はハックの多くですが、使い始める方が簡単です。デリゲートが制御不能になると、いつでもデリゲートに切り替えることができます。

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

関連する問題