2010-03-09 117 views
13

我正在为我正在编写的应用程序开发一个平台抽象库,并努力想出将平台独立代码与平台特定码。跨平台C++代码体系结构

正如我所看到的,可能有两种基本方法:与平台相关的代理平台无关的类或平台相关的派生类。这两种方法都有其固有的优点/缺点吗?无论哪种情况,建立委派/继承关系的最佳机制是什么?这样的过程对平台独立类的用户是透明的?

我会很感激任何关于整洁架构的建议,甚至只是一些人们过去做过的事情以及给定方法的利弊。

编辑:对于那些暗示Qt和类似的人,是的,我故意寻找“重新发明轮子”,因为我不只是关心开发应用程序,我也对滚动的智力挑战感兴趣我自己的平台抽象库。还是)感谢你的建议!

回答

11

我正在使用平台中性头文件,在源文件中保留任何平台特定的代码(使用PIMPL idiom必要的地方)。每个平台中立标题每个平台都有一个平台特定的源文件,其扩展名如*.win32.cpp,*.posix.cpp。平台特定的平台仅在相关平台上编译。

我也使用boost库(文件系统,线程)来减少我必须维护的平台特定代码的数量。

它是与平台无关的类声明,具有特定于平台的定义。

优点:作品相当好,不依赖于预处理器 - 没有#ifdef MyPlatform,保持平台特定的代码易于识别,可以让编译器的特定功能在特定平台的源文件使用,不污染全局命名空间通过#including平台标题。

缺点:很难在pimpled类中使用继承,有时PIMPL结构需要自己的头文件,因此可以从其他平台特定的源文件中引用它们。

+0

要扔在进一步阅读一些检索词:我觉得这也被称为“桥模式”或“实现模式”。 – foraidt 2010-03-10 00:02:52

+0

有趣,这是我一直在做的。我有一个问题,如果你有两个平台,其中90%的代码在该类/文件的平台之间是相同的? – matt 2010-03-10 00:30:58

+2

@matt:您可以使用通用的源文件; foo.h(平台无关),foo.cpp(包含公共代码),foo.win32.cpp,foo.posix.cpp。根据foo.cpp包含的内容,它可以被单独编译然后链接,或者只包含到foo.win32.cpp和foo.posix.cpp – 2010-03-10 00:50:13

4

看看ACE。它使用模板和继承有很好的抽象。

4

另一种方法是使平台独立约定,但在编译时替换平台特定的源代码。

也就是说,如果你想象组件,Foo,那必须是平台特定的(如插座或GUI元素),但这些公共成员:

class Foo { 
public: 
    void write(const char* str); 
    void close(); 
}; 

有每个模块使用Foo,显然有#include "Foo.h",但是在特定于平台的make文件中,您可能有-IWin32,这意味着编译器在.\Win32中查找并找到Windows特定的Foo.h,其中包含具有相同接口的类,但可能是Windows特定的专用会员等

所以从来没有包含如上书面Foo的任何文件,但是当通过特定于平台的make文件选择其仅用于特定平台的文件只套。

0

你可能也想看看poco

的POCO C++库(POCO代表可移植的组件)是简化和加速网络为中心的,便携的开发开源C++类库应用程序在C++中。这些库与C++标准库完美集成,并填充许多由它打开的功能差距。其模块化和高效的设计和实现使得POCO C++库非常适合嵌入式开发,这是C++编程语言由于其适用于低级(设备I/O,中断处理程序等)而变得越来越流行的领域)和高级别的面向对象开发。当然,POCO C++库也可以应对企业级挑战。

poco architecture http://pocoproject.org/images/poco.png

2

我可能会去一个政策性的东西:

template<typename Platform> 
struct PlatDetails : private Platform { 
    std::string getDetails() const { 
     return std::string("MyAbstraction v1.0; ") + getName(); 
    } 
}; 

// For any serious compatibility functions, these would 
// of course have to be in different headers, and the implementations 
// would call some platform-specific functions to get precise 
// version numbers. Using PImpl would be a smart idea for these 
// classes if they need any platform-specific members, since as 
// Joe Gauterin says, you want to avoid your application code indirectly 
// including POSIX or Windows system headers, containing useless definitions. 
struct Windows { 
    std::string getName() const { return "Windows"; } 
}; 

struct Linux { 
    std::string getName() const { return "Linux"; } 
}; 

#ifdef WIN32 
    typedef PlatDetails<Windows> PlatformDetails; 
#else 
    typedef PlatDetails<Linux> PlatformDetails; 
#endif 

int main() { 
    std::cout << PlatformDetails().getName() << "\n"; 
} 

有没有一个整体很多选择,虽然这样做,并定期做模拟动态与CRTP结合之间,所以通用事物是派生类的基础和具体事物:

template<typename Platform> 
struct PlatDetails { 
    std::string getDetails() const { 
     return std::string("MyAbstraction v1.0; ") + 
      static_cast<Platform*>(this)->getName(); 
    } 
}; 

struct Windows : PlatDetails<Windows> { 
    std::string getName() const { return "Windows"; } 
}; 

struct Linux : PlatDetails<Linux> { 
    std::string getName() const { return "Linux"; } 
}; 

#ifdef WIN32 
    typedef Windows PlatformDetails; 
#else 
    typedef Linux PlatformDetails; 
#endif 

int main() { 
    std::cout << PlatformDetails().getName() << "\n"; 
} 

基本上在后一版本中,getName必须是公开的(尽管我认为你可以使用friend),所以必须是继承,而在前者中,继承可以是私有的和/或可以保护接口函数(如果需要的话)。所以适配器可以是平台必须实现的接口和应用程序代码使用的接口之间的防火墙。此外,前者可以有多个策略(即相同平台无关的类使用多个平台相关的方面),但不适用于后者。

它们中的任何一个优于具有委托或非模板使用继承的版本的优点是您不需要任何虚拟功能。可以说,这不是一个很大的优势,考虑到基于策略的设计和CRTP最初接触的可怕程度。

但在实践中,我同意quamrana,通常你可以有同样的事情,不同的实现在不同的平台:

// Or just set the include path with -I or whatever 
#ifdef WIN32 
    #include "windows/platform.h" 
#else 
    #include "linux/platform.h" 
#endif 

struct PlatformDetails { 
    std::string getDetails() const { 
     return std::string("MyAbstraction v1.0; ") + 
      porting::getName(); 
    } 
}; 

// windows/platform.h 
namespace porting { 
    std::string getName() { return "Windows"; } 
} 

// linux/platform.h 
namespace porting { 
    std::string getName() { return "Linux"; } 
} 
+0

heh,只要Linux和Windows是你唯一的两个平台:) 我明白这只是一个简单的例子,但它决定我们需要另一个平台时使用'ifdef平台其他'造成的痛苦可能很容易避免。所以如果使用ifdefs,我会建议你不要假设你不会添加任何更多的平台,当然#if定义plat1 #elif defined plat2 #else #error #endif – matt 2010-03-10 00:35:59

+0

当然。无论是兼容性层的每个组件都有一个ifdef事物,还是只有一个能够设置所有事物的大事件,它应该肯定地识别每个平台,而不是像我一样对最后一个平台懒惰。使用平台相关包含路径的优点之一是不必做任何这样的ifdeffery。 – 2010-03-10 00:58:27

0

如果你喜欢使用可用于许多一个成熟的C++框架平台和宽容copylefted,使用Qt。

0

所以......你不想简单地使用Qt?对于使用C++的实际工作,我会非常推荐它。这是一个绝佳的跨平台工具包。我只是写了几个插件,让它在Kindle上工作,现在是Palm Pre。 Qt使一切变得简单而有趣。彻底恢复活力,甚至。那么,直到你第一次遇到QModelIndex,但他们已经意识到他们过度设计了它,并且他们正在取代它)

虽然作为一个学术练习,这是一个有趣的问题。作为我自己的轮子重新发明者,我现在甚至做了几次。 :)

简短的回答:我会去PIMPL。(Qt的来源有例子,大量的)

我用基类和特定平台在过去的派生类,但它通常结束了一点混乱比我的初衷。我还使用某种程度的平台特定位的函数指针来完成实现的一部分,我对此更不满意。

这两次我结束了一个很强烈的感觉,我是在设计架构也失去了我的路。

我发现使用私有实现类(PIMPL)与不同平台的具体位在不同的文件中最容易编写调试。然而......不要太害怕#ifdef或两个,如果它只是几行,而且很清楚发生了什么。我讨厌混乱或嵌套#ifdef逻辑,但这里和那里的一两个真的可以帮助避免代码重复。

随着PIMPL,你不是不断重构你的设计,你发现需要平台之间不同的实现新的位。这就是龙。

在执行层面,从应用程序中隐藏的...有什么不对的几个平台特定的派生类两种。如果两个平台实现的定义相当明确,并且几乎不共享数据成员,那么他们将会是一个很好的候选人。只要意识到,而不是出于某种想法,一切都需要适合您选择的模式。

如果有什么事情,我今天编码最大的抱怨是多么让人看起来很容易迷失在理想主义。 PIMPL是一种模式,具有特定于平台的派生类是另一种模式。使用函数指针是一种模式。没有什么说他们是互斥的。

但是,作为一般指导方针......从PIMPL开始。

0

有很多跟大男孩,如Qt4(完整的框架+ GUI),GTK+(GUI-仅据我所知),并Boost(只有框架,没有GUI),所有3个支持大多数平台,GTK +是C, Qt4/Boost是C++,大部分是基于模板的。