2011-09-20 19 views
5

私は、クロスプラットフォームクラスを書く方法を見つけようとしていますが、プラットフォーム固有のバージョンのクラスでは、仮想関数のコストやあらゆる種類の醜さを避けています。ここで私が試したことがあります。C++このクラス構造はどのように達成できますか?

PlatformIndependantClass.hpp

class PlatformIndependantClass { 
    public: 
     PlatformIndependantClass(); 
     std::string GetPlatformName(); 
    private: 
     PlatformIndependantClass* mImplementation; 

}; 

LinuxClass.hpp

#include "PlatformIndependantClass.hpp" 
class LinuxClass : public PlatformIndependantClass{ 
    public: 
     std::string GetPlatformName(); 
}; 

WindowsClass.hpp

#include "PlatformIndependantClass.hpp" 
class WindowsClass : public PlatformIndependantClass { 
    public: 
     std::string GetPlatformName(); 
}; 

PlatformIndependantClass.cpp

#include "PlatformIndependantClass.hpp" 
#include "LinuxClass.hpp" 
#include "WindowsClass.hpp" 
PlatformIndependantClass::PlatformIndependantClass() { 
    #ifdef TARGET_LINUX 
     mImplementation = new LinuxClass(); 
    #endif 
    #ifdef TARGET_WINDOWS 
     mImplementation = new WindowsClass(); 
    #endif 
} 
std::string PlatformIndependantClass::GetPlatformName() { 
    return mImplementation->GetPlatformName(); 
} 

LinuxClass.cpp

#include "LinuxClass.hpp" 
std::string LinuxClass::GetPlatformName() { 
    return std::string("This was compiled on linux!"); 
} 

WindowsClass.cpp

#include "WindowsClass.hpp" 
std::string WindowsClass::GetPlatformName() { 
    return std::string("This was compiled on windows!"); 
} 

main.cppに

#include <iostream> 
#include "PlatformIndependantClass.hpp" 

using namespace std; 

int main() 
{ 
    PlatformIndependantClass* cl = new PlatformIndependantClass(); 
    cout << "Hello world!" << endl; 
    cout << "Operating system name is: " << cl->GetPlatformName() << endl; 
    cout << "Bye!" << endl; 
    return 0; 
} 

これでコンパイルは正常ですが、セグメント化エラーが発生します。これは、プラットフォーム固有のクラスがPlatformIndependantClassを継承しているためです。構築時に、プラットフォーム固有のクラスのインスタンスが作成されるため、無限再帰が発生します。私が試してみるたびに、私はちょうど非常に混乱する!

どうすればこのようなデザインを適切に達成できますか?それともこれは単なる恐ろしい考えですか?私はクロスプラットフォームのクラスを書く方法を見つけようとしていましたが、クロスプラットフォームライブラリに関する結果が得られました。どんな助けも喜んで受け入れられます:)

+0

「プラットフォームの無関係」とはどういう意味ですか? –

+2

バーチャルファンクションはどれくらいの費用がかかりますか?ユーザーが気付くには十分ですか? – Mark

+1

PS: "independent"には "a"がありません。 –

答えて

6

私はあなたが達成しようとしているものだと思うはるかに容易に行うことができるに...

Object.h:

#include <normal includes> 

#if WINDOWS 
#include <windows includes> 
#endif 

#if LINUX 
#include <linux includes> 
#endif 

class Object 
{ 
private: 

#if WINDOWS 
//Windows Specific Fields... 
#endif 

#if LINUX 
//Linux Specific Fields... 
#endif 

public: 
    //Function that performs platform specific functionality 
    void DoPlatformSpecificStuff(); 

    //Nothing platform specific here 
    void DoStuff();  
}; 

Object.cpp

#include "Object.h" 

void Object::DoStuff() { ... } 

ObjectWin32.cpp

#if WINDOWS 

#include "Object.h" 

void Object::DoPlatformSpecificStuff() 
{ 
    //Windows specific stuff... 
} 

#endif 

ObjectLinux.cpp

#if LINUX 

#include "Object.h" 

void Object::DoPlatformSpecificStuff() 
{ 
    //Linux specific stuff... 
} 

#endif 

のように。私はこれがあなたが少し楽にしていることを達成できると思います。また、仮想関数は必要ありません。

+0

+1は、私の複数のライブラリのアイデアから離れて、私は離れすぎているC + +のために余りにも長いです:) – Blindy

+1

+1正気を追加することは良い解決策をもたらす傾向があります。 –

+0

@Blindy:しばらくの間C++から抜け出したいと思うことがありますが、いつも戻ってきます...--) – James

6

本当に恐ろしいアイデアです。 「仮想機能のコストを避けたい」という大部分のアイデアと同様です。

セグメンテーションフォルト(具体的にスタックオーバーフロー)が発生する理由は、仮想関数を使用せずに静的リンクを使用しているからです。コンパイラは、がPlatformIndependantClass以外のものであることを知らないので、return mImplementation->GetPlatformName()を呼び出すと、何度も同じ関数を呼び出すことになります。

あなたが達成したことは、シャドウイングと呼ばれ、コンパイル時の関数解決を使用しています。コンパイラはから呼び出す変数の実際の型のGetPlatformName関数を呼び出します。これは実際の関数へのポインタを上書きする仮想テーブルがないためです。 mImplementationPlatformIndependantClassなので、mImplementation->GetPlatformNameは常にPlatformIndependantClass::GetPlatformNameになります。

編集もちろん、WindowsとLinuxの両方のコピーを同時に作成する必要があるのは当然のことです。あなたは同時にそれらの両方を使用することはありません、そうですか?

なぜシステムごとに2つの異なるライブラリがあり、正しいものがメイクファイルからリンクされていないのでしょうか。あなたはすべての世界の中で最高のものを手に入れます!

+0

segfaultの説明はとても役に立ちました。なぜ私はそれを認識していませんでしたか?しかし、私はクロスプラットフォームコードについては別のアプローチをとると思いますが、提案に感謝します。 – Ell

1

代わりにプラットフォーム固有のインスタンスを構築するコンストラクタを使用して、私はインスタンスを作成するために、静的なファクトリメソッドを作成します。

PlatformIndependantClass* PlatformIndependantClass::getPlatformIndependantClass() { 
    #ifdef TARGET_LINUX 
     return new LinuxClass(); 
    #endif 
    #ifdef TARGET_WINDOWS 
     return new WindowsClass(); 
    #endif 
} 

あなたは再帰を避けるため、この方法を、あなたも必要ありません。あなたのmImplementationポインタ。

私はまた、プラットフォーム固有のクラスを避けるためにしようとするだろうが、それはまた別の話だ:)

+0

これは私の最初のアイデアの1つでしたが、私はこれで終わるかもしれませんが、明示的な関数があるという事実が気に入らない、私がしようとしていたのは...暗黙的なコンストラクタ(それがあなたに意味をなされた場合) – Ell

+0

仮想テーブルがない場合を除いて、依然として仮想呼び出しを取得することはできません。 – Puppy

0

コンストラクタが無限再帰を引き起こしているとは思わない。これはGetPlatformName()関数です。仮想として設定されていないため、自分自身を呼び出すことができます。

2つの解決策:その機能を仮想化するか、継承を完全に廃止する。

いずれにせよ、別の関数を呼び出す関数のコストは、最初に仮想関数を使用するよりも高価になります。したがって、継承を維持し、プラットフォーム固有の関数を仮想化し、基本クラスの関数を経由せずに直接呼び出すといいでしょう。

1

ランタイムオーバーヘッドなしで多態的な動作をしたい場合は、curiously recurring template pattern (CRTP)を試すことができます。基本クラスはテンプレートであり、派生クラスはそれ自体を基底のテンプレートパラメータとして使用します。これには、クラスをテンプレートとして定義する必要があり、ヘッダー(.hpp)ファイルに完全に実装されるように制限します。

特定のケースでパターンを適用する方法がわかりません。

0

infinteループについては正しいですか。修正は実際にはあなたが考えるよりも簡単です。

PlatformIndependantClass.hpp

#include //portable headers 
struct PlatformDependantClass; //defined in Cpp file 
class PlatformIndependantClass { 
    public: 
     PlatformIndependantClass(); 
     ~PlatformIndependantClass(); 
     std::string GetPlatformName(); 
    private: 
     std::unique_ptr<PlatformDependantClass> mImplementation; //note, different type 

}; 

LinuxClass.cpp

#ifdef __GNUC__ 
#include //linux headers 
#include "PlatformIndependantClass.hpp" 
struct PlatformDependantClass { //linux only stuff 
    //stuff 
};  

PlatformIndependantClass() { 
    mImplementation.reset(new PlatformDependantClass); 
} 
~PlatformIndependantClass() { 
} 
std::string PlatformIndependantClass::GetPlatformName() { 
    return std::string("This was compiled on linux!"); 
} 
#endif //__GNUC__ 

WindowsClass。

#ifdef _MSC_VER 
#include //windows headers 
#include "PlatformIndependantClass.hpp" 
struct PlatformDependantClass { //windows only stuff 
    //stuff 
};  

PlatformIndependantClass() { 
    mImplementation.reset(new PlatformDependantClass); 
} 
~PlatformIndependantClass() { 
} 
std::string PlatformIndependantClass::GetPlatformName() { 
    return std::string("This was compiled on Windows!"); 
} 
#endif //_MSC_VER 

CPPここで定義されただけONEクラスがあります。ウィンドウズでは、コンパイルしてウィンドウズのものしか入っていません.Linuxでは、コンパイルしてLinuxのものしか含んでいません。 void*のことを「不透明ポインタ」または「pimplイディオム」と呼びます。http://en.wikipedia.org/wiki/Opaque_pointer

+0

-1、どのようにポインタを 'void *' "簡単な修正"にしていますか? 'delete mImplementation'はコンパイルされず、あなたのクラス関数は再定義されません。 – Blindy

+0

実際にコメントする前に気づいて修正しました。私はvoid *でpImplを見たことがあると断言できましたが、間違ってしまったので、私が入れたwikiリンクから自分自身を修正しました。 –

関連する問題