2016-08-01 26 views
3

私は異なるプラットフォーム上で自分のコードを実行できるように抽象レイヤーを作成しようとしています。私は最終的にハイレベルのコードで使用する二つのクラスのための例を挙げてみましょう:デザインパターン:C++抽象レイヤー

class Thread 
{ 
public: 
    Thread(); 
    virtual ~Thread(); 

    void start(); 
    void stop(); 

    virtual void callback() = 0; 
}; 

class Display 
{ 
public: 
    static void drawText(const char* text); 
}; 

私の悩みはある:どのようなデザインパターンは、私が実装における低レベルのコードの塗りをさせるために使用することができますか? は、ここに私のthoughsあると私は考えていないなぜ彼らは優れたソリューションです。理論的には

  1. lowLevel/platformA/thread.cppに座っhighLevel/thread.hにおける上記の定義に座ると、プラットフォーム固有の実装を持つには問題はありません。これは、リンク時に解決される低オーバーヘッドのソリューションです。唯一の問題は、低レベル実装ではメンバ変数やメンバ関数を追加できないということです。これにより、実装が不可能な特定のものが作成されます。アウト

  2. 方法が定義(基本的にPIMPL-イディオム)にこれを追加することです:

    class Thread 
    { 
        // ... 
    private: 
        void* impl_data; 
    } 
    

    今、低レベルのコードは、それがvoidポインタに保存されている独自の構造体またはオブジェクトだことができます。ここで問題となるのは、プログラムを読むのが面倒で痛いということです。

  3. 私はclass Threadを純粋仮想とし、それを継承して低レベルの機能を実装することができます。ハイレベルのコードは次のようにファクトリ関数を呼び出すことによって、低レベルの実装にアクセスできます。

    // thread.h, below the pure virtual class definition 
    extern "C" void* makeNewThread(); 
    
    // in lowlevel/platformA/thread.h 
    class ThreadImpl: public Thread 
    { ... }; 
    
    // in lowLevel/platformA/thread.cpp 
    extern "C" void* makeNewThread() { return new ThreadImpl(); } 
    

    これは十分に整頓だろうが、それは、静的なクラスのために失敗しました。私の抽象化レイヤーはハードウェアとIOのために使用されますが、私は実際にDisplayクラスへのポインタを持ち歩く代わりにDisplay::drawText(...)を持つことができるようにしたいと考えています。

  4. もう1つの選択肢は、このようなリンク時に解決できるCスタイルの関数だけを使用することです。extern "C" handle_t createThread()。これは、ディスプレイのように1度しかない低レベルのハードウェアにアクセスするのは簡単で素晴らしいです。しかし、ロック(スレッド、スレッド、メモリ管理)を何度も行うことができる何かのために、私は醜い高レベルのコードでハンドルを持ち歩かなければならないか、ハンドルを隠す高水準のラッパークラスを持っています。どちらの方法でも、高レベルと低レベルの両方のそれぞれの機能にハンドルを関連付ける必要があるというオーバーヘッドがあります。

  5. 私の最後の考えはハイブリッド構造です。純粋なCスタイルのextern "C"は、1度しか存在しない低レベルのもののために機能します。複数回あることができるもののためのファクトリ関数(3.を参照)。しかし、私は何かハイブリッドが一貫性​​のない、判読不能なコードにつながるのではないかと心配しています。

私の要件に合ったパターンを設計するためのヒントには非常に感謝しています。

+4

はまだあなたの全体の質問を読んでいない:

また、私はクラスの継承の階層を作成する傾向があり、一部の人々は、この反対助言することを、予めご了承ください。しかし、C++には、C++ 11以降の抽象化レイヤーがあります。 – StoryTeller

+0

私はFreeRTOSで埋め込まれたターゲットに取り組んでいます。もう1つのプラットフォームは、高水準コードの開発を容易にするコンピュータベースのエミュレーションです。私はC++ 11がこのシナリオをサポートしているとは思わない。 – LoveDaOOP

答えて

0

あなたのThreadクラスのための価値のセマンティクスがほしいと思うし、どこに間接参照を追加してポータブルにするのか疑問に思えます。だから、あなたはpimplイディオムといくつかの条件付きコンパイルを使用します。
あなたのビルドツールの複雑さになりたい場所に応じて、あなたは可能性として含まれている自己として、すべての低レベルのコードを維持したいならば、あなたは次のようにします。あなたのハイレベル・ヘッダーThread.hpp

、あなたは定義:

class Thread 
{ 
    class Impl: 
    Impl *pimpl; // or better yet, some smart pointer 
public: 
    Thread(); 
    ~Thread(); 
    // Other stuff; 
}; 

あなたのスレッドのソースディレクトリに、あなたはこのようなやり方に沿ってファイルを定義より:

Thread_PlatformA.cpp

#ifdef PLATFORM_A 

#include <Thread.hpp> 

Thread::Thread() 
{ 
    // Platform A specific code goes here, initialize the pimpl; 
} 

Thread::~Thread() 
{ 
    // Platform A specific code goes here, release the pimpl; 
} 

#endif 

Building Thread.oは、すべてのThread_*.cppファイルをスレッドディレクトリに入れて、ビルドシステムにコンパイラの正しい-Dオプションが指定されているという単純な問題になります。

+0

はい、見ました前にPimplのイディオム。基本的に私の最初の投稿の2です。私は真剣にこれを行うと考えていますが、コードが実際には醜いものになっています。 – LoveDaOOP

+0

@LoveDaOOP、あなたが 'void * 'を使っているならば。あなたがタイプシステムを迂回しなければ、それほど多くはない。 – StoryTeller

+0

私はこれを答えとして受け入れています。なぜなら、これは最もきれいな解決策だと思われるからです。私のプロジェクトでは、最終的に単純なCスタイルのインターフェイス(私の最初の投稿では4.)に行きました。高レベル側では、すべてのハンドルを隠すラッパークラスがあります。 'typedef void * threadHandle_t'としてハンドルを定義することで、低レベルのコードでハンドルを適切なものとして使用することができます。しばしば、ハンドルは、低レベルコード内のオブジェクトへのポインタであると悪用されます。そうすれば、低レベル側のそれぞれのオブジェクトにハンドルを関連付けるためにテーブルやマップを使用する必要はありません。皆さんありがとう! – LoveDaOOP

1

コードは一度に1つのコンクリートプラットフォーム用にのみコンパイルされるため、プラットフォームに依存しない基本クラスは必要ありません。

ただ、例えば、 -Iinclude/generic -Iinclude/platformへのパスが含まれ、それぞれに別々のThreadクラスを持っている設定

プラットフォーム年代のディレクトリを含めサポート。

デフォルトで実行される&コンパイルされたプラットフォームに依存しないテストを書くことができます(これは、異なるプラットフォーム固有の実装が同じインターフェイスとセマンティクスに従うことを確認することができます)。

PS。 StoryTellerが言うように、スレッドは既に携帯性があるので、悪い例ですstd::thread。私はあなたが抽象化する必要がある他のプラットフォーム固有の詳細がいくつかあると仮定しています。

PPS。 generic(プラットフォームに依存しない)コードとプラットフォーム固有のコードの間の正確な分割を把握する必要があります。どこに行くかを決めるための魔法の弾丸はありません。再利用/複製、単純化と高度パラメータ化のトレードオフコードなど

+1

もちろん、別の 'Thread'クラスではなく、' Thread'クラスの別々の*実装*です。 – StoryTeller

+0

2つの異なるクラス(両方とも 'Thread'と呼ばれます)を別々のディレクトリに書き込むと、それぞれから同じパブリックインターフェイスが公開されますが、それらを別々に実装し、確実に実装します。 _only one_は、指定された翻訳単位とプログラム全体に含めることができます...彼らは異なるクラスですか?同じクラスの異なる実装ですか?それは有用な違いなしの区別です。 – Useless

+0

ODRには、そのようなクラスがシステム内に1つしかないと言われています。そしてそれは高レベルのヘッダーで定義されています。実装は異なる場合があります。セマンティクスを正確に把握することで、OPが彼らが何をする必要があるかを理解するのに役立ちます。 – StoryTeller

0

私は好奇心、それが(ちょうど糸に付着し)、次のようにこのような状況を設計するために好きなものを次のようになります。

実装については何も入っていない
// Your generic include level: 
// thread.h 
class Thread : public 
#ifdef PLATFORM_A 
    PlatformAThread 
#elif PLATFORM_B 
    PlatformBThread 
// any more stuff you need in here 
#endif 
{ 
    Thread(); 
    virtual ~Thread(); 

    void start(); 
    void stop(); 

    virtual void callback() = 0; 
} ; 

、ちょうどインタフェース

次に、あなたが持っている:

// platformA directory 
class PlatformAThread { ... }; 

、これは自動的に自動的グラムあなたは「汎用」Threadオブジェクトを作成するときにということとなりますプラットフォーム固有のクラスで、プラットフォーム固有の操作を持つ可能性があります。もちろん、PlatformAThreadクラスは一般的なものを持つ汎用Baseクラスから派生する可能性があります。

また、プラットフォーム固有のディレクトリを自動的に認識するようにビルドシステムを設定する必要があります。 https://en.wikipedia.org/wiki/Composition_over_inheritance

+0

それは良い考えです。しかし、それは、高レベルのコードがどのプラットフォーム上で実行されるかを知っていることを前提としています。私はstringyficationでマクロを使用して、makefileを介して正しいクラス名を提供することができました。このように: 'class Thread:public TREAD_IMPL' ビルドの設定では、適切なプリプロセッサ定義を追加できます。しかしbutsも一種の乱雑です。 – LoveDaOOP

関連する問題