2011-09-20 59 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

你是什麼意思的「平臺indenpendence」? –

+2

虛擬功能的成本是多少 - 這對用戶來說是否足夠注意? – Mark

+1

PS:「獨立」在其中沒有「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

@Bindind:有時候我希望遠離C++一段時間,但我總是回來...... :-) – James

6

從結尾開始,是的,真的是一個可怕的想法,就像大多數以「我想避免虛擬功能的成本」開頭的想法一樣。至於爲什麼你會得到分段錯誤(特別是堆棧溢出),這是因爲你沒有使用虛擬函數,而是使用靜態鏈接。編譯器不知道mImplementation只是一個PlatformIndependantClass,所以當您嘗試調用return mImplementation->GetPlatformName()時,您一次又一次地調用相同的函數。

你實現的結果叫做shadowing,你正在使用編譯時函數解析。因爲沒有虛擬表來覆蓋指向實際函數的指針,所以編譯器將調用函數來自的變量的實際類型。由於mImplementationPlatformIndependantClass,mImplementation->GetPlatformName將始終爲PlatformIndependantClass::GetPlatformName

編輯:當然,爲什麼需要同時創建引擎的Windows和Linux副本的問題浮現在腦海。你永遠不會同時使用他們兩個,對吧?

那麼,爲什麼不只是有兩個不同的庫,每個系統一個,並從你的makefile鏈接正確的。你獲得最好的世界!

+0

segfault的解釋非常有用,我不知道爲什麼我沒有意識到這一點!但我想我會在跨平臺代碼上採用不同的方法,儘管感謝您的建議。 – Ell

1

而不是使用構造函數構建特定於平臺的實例,我想創建一個靜態的工廠方法來創建實例:

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

這樣你可以避免遞歸,你也不需要你的實現指針。

我也儘量避免特定於平臺的類,但這是另一個故事:)

+0

這是我最初的想法之一,我可能最終會與之一起,我只是不喜歡有明確的功能的事實,我想我正在嘗試做的是使...隱式工廠使用構造函數(如果這對您有意義) – Ell

+0

除非沒有虛擬表,否則您仍然無法獲得虛擬調用。 – Puppy

0

我不認爲構造函數導致無限遞歸。它是GetPlatformName()函數。因爲它不是虛擬的,所以只能調用它自己。

兩種解決方案:使該功能變爲虛擬,或完全廢除繼承。

無論哪種方式,只調用另一個函數的函數的開銷將比首先使用虛函數的開銷更大。所以我會說保留繼承,並虛擬化特定於平臺的函數,並直接調用它們,而不需要通過基類函數。

1

如果您希望具有多態行爲而沒有任何運行時間開銷,則可以嘗試curiously recurring template pattern (CRTP)。基類是一個模板,派生類使用它自己作爲基的模板參數。這要求將您的類定義爲模板,這進一步限制了它們完全在頭文件(.hpp)中實現。

我不確定如何在特定情況下應用該模式。

0

您對無限循環是正確的。該修復實際上比您想象的要容易。

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。CPP

#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 

這裏只有ONE這裏定義的類。在Windows中,它只編譯和包含Windows的東西,而在Linux中,它只編譯和包含Linux的東西。請注意,void*的東西被稱爲「不透明指針」或「pimpl成語」http://en.wikipedia.org/wiki/Opaque_pointer

+0

-1,如何使指針成爲'void *'「簡單修復程序」? 'delete mImplementation'甚至不會編譯,您的類的函數也不會重新定義... – Blindy

+0

在您實際評論之前,我注意到並糾正了它。我可以發誓我已經看到pImpl帶有void *,但是我做錯了,我從我放入的維基鏈接糾正了自己。 –