2013-01-29 5 views
9

LinuxとWindowsでの実行をサポートするプロジェクトを維持する必要があります。このようなプリプロセッサディレクティブを使用しているいくつかのコードは問題ありません。プリプロセッサディレクティブを避けるためのC++クロスプラットフォーム開発

#ifdef _WIN32 // _WIN32 is defined by Windows 32 compilers 
#include <windows.h> 
#else 
#include <unistd.h> 
#endif 

しかし実際にはプリプロセッサディレクティブを使用しないようにする実装もあります。

void Foo() 
{ 

    #ifdef _WIN32 // _WIN32 is defined by Windows 32 compilers 
    code for windows 
    #else 
    code for Linux 
    #endif 

    some common code... 

    #ifdef _WIN32 // _WIN32 is defined by Windows 32 compilers 
    code for windows again 
    #else 
    code for Linux again 
    #endif 

} 

物事は複雑になり、維持するのが難しくなります。もっと良い方法はありますか?

+6

インターフェイス+抽象化レイヤプラットフォーム固有のコードが、現在ビルドしているプラ​​ットフォームに基づいてビルドシステムによって除外できる実装ファイルに含まれています。 – Chad

+1

現実世界の例が必要な場合は、Doom3ソースを見てみるとよいでしょう。@Chadが説明したものと似ています。 https://github.com/TTimo/doom3.gpl/tree/master/neo/sys –

+3

標準ライブラリを守ることは、あなたにとって長い道のりです。私がこれまで取り組んできた最も普及したマルチプラフォームプロジェクトは、11の異なるターゲットを対象としていました。最終的には避けることができないものがいくつかあります(AS/400上の動的ライブラリロードなど)。信じることを見て)。他に何も利用可能でない限り、プラットフォームごとに '#ifdef'コーディングを避けてください。あなたがそれを他の選択肢のないシナリオと見なしている場合は、*機能*ベースでそれを実行してください。 Ex。あなたのコード: '#ifdef HAS_STDINT_H'など – WhozCraig

答えて

8

伝統的な方法は、ラッパー関数内の任意のOSに固有のすべてのコードを「隠す」ことです。より高いレベルの機能を果たす完全な関数で行うことができます。指定されたパスに基づいてすべてのディレクトリエントリを入力として返す関数を持つか、個々の基底関数を実装する。 start_read_directory(path)read_dir_entry()end_read_directory() - これは単なる例示的な機能ですが、ほぼ同じシステム特有の機能にも同じ原則を適用できます。十分にラップすれば、あなたは何をプログラミングしているのかを知ることができません。

コード自体に#ifdefがたくさんある場合は、本質的に間違っています。

5

ファクトリパターンの簡略化されたバージョンを使用できます。

は、あなたがこのクラスを必要とするときに、あなたはあなたの工場を使用

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

そして、各プラットフォームのためにあなたが特定のクラスに

#import <windows.h> 
class MyClassWindows : MyClass 
{ 
public: 
    virtual void Foo() { /* Do something */ } 
}; 

#import <linux.h> 
class MyClassLinux : MyClass 
{ 
public: 
    virtual void Foo() { /* Do something */ } 
}; 

を作成する共通のインタフェースを持っている:

class MyClassFactory 
{ 
public: 
    static MyClass* create() 
    { 
     #if defined _WIN32 
      return new MyClassWindows(); 
     #elif defined _LINUX 
      return new MyClassLinux(); 
     #endif 
    } 
} 

このメソッドには、MyClassFactory :: createメソッドを.cppの中に定義することを含む多くのバリエーションがあります。各プラットフォーム固有のクラスを作成し、適切なプラットフォーム用の.cppファイルのみをコンパイルします。これにより、すべての前処理ディレクティブが回避され、切り替えは正しいインプリメンテーションファイルを選択することによって行われます。

+2

これはランタイム仮想関数のコストも導入していません2つのプラットフォームで同時に実行することがないときには、いくらか余分な継承のレイヤーとして使用できますか? – Digikata

+1

それはそうですが、私は読みやすさとメンテナンスの容易さのためにわずかな費用です(これは私の意見です、それは主観的です)。継承をスキップし、いくつかの回答で提案されているのと同じヘッダーファイルに対して異なる.cppファイルを実装するだけで、新しいメソッドを追加して一部のプラットフォームで忘れると、コンパイルエラーの代わりにリンクエラーが発生する可能性があります。さらに、基本クラスのすべての共通コードを共有することができ、どのファイルにどのメソッドが含まれているかが明確です。最後に、プラットフォームにいくつかのプライベートメソッドが必要な場合、それらはプラットフォーム固有のクラスに入れるのが簡単です。 –

8

コードではなく、ビルドシステムからOSの詳細を処理します。たとえば、Foo.cppの2つのバージョンがあります.1つはLinuxでコンパイルされ、もう1つはWindowsでコンパイルされます。理想的には、ヘッダーファイルは共通であり、すべての関数シグネチャは同一です。

4

一般的なパターンは、システムに依存しないヘッダーファイルとプラットフォーム固有の実装ファイルを提供することです。

ヘッダー内の特定のプラットフォームナッシング:二つの異なる実装ファイルで

class Foo 
{ 
    ... 
}; 

、foo_linux.cpp

Foo::Foo() { .. linux code } 

foo_windows.cpp

Foo::Foo() { .. windows code } 

、多分プラットフォームに依存しない実装でfoo.cpp

void Foo::plat_independent_function() 

プラットフォームは、foo.cppとfoo_platformでリンクします。cpp

0

これはPIMPLイディオムを使用することです。あなたのクラスが単に「インターフェイス」を公開し、インプリメンテーションクラス(暗く隠れたプライベートコーナーにある)とビルドの意味のないポインタを宣言しますシステムは、PIMPLの実装を含むクラスの正しいプラットフォームに依存するコードを取得します。

関連する問題