跨平台C ++代码和单头 - 多个实现

时间:2013-08-14 17:33:57

标签: c++ cross-platform header-files

我听说编写跨平台c ++代码的方法是按如下方式定义类(例如,一个Window类):

window.h
window_win32.cpp
window_linux.cpp
window_osx.cpp

然后相应地选择实现文件。 但是,如果我有那个类的成员相对于操作系统呢?就像Win32实现的HWND成员一样。我不能把它放在window.h中,或者当我试图在Linux上编译它时,它会产生编译错误。

我需要#ifdef吗?我已经问了一个类似的问题,但是这个问题更关注这个问题。

5 个答案:

答案 0 :(得分:22)

有更多方法可以解决这个问题 - 每个方法都有它的优点和缺点。

1。)使用宏#ifdef,#endif

// Note: not sure if "WINDOWS" or "WIN32" or something else is defined on Windows
#ifdef WINDOWS
    #include <window.h>
#else
    // etc.
#endif

class MyClass
{
public:

    // Public interface...

private:

#ifdef WINDOWS
    HWND m_myHandle;
#else
    // etc.
#endif

};

优点:

  • 程序的最大速度。

缺点:

  • 更糟糕的可读性。有许多平台,它可能会变得非常混乱。
  • 包含特定于平台的内容可能会破坏某些内容。 windows.h定义了许多具有普通名称的宏。

2。)正如已经写过的那样,你可以使用多态:

// IMyClass.h for user of your class:

class IMyClass
{
public:

    virtual ~IMyClass() {}
    virtual void doSomething() = 0;

};


// MyClassWindows.h is implementation for one platform

#include <windows.h>
#include "IMyClass.h"

class MyClassWindows : public IMyClass
{
public:

    MyClassWindows();
    virtual void doSomething();

private:

    HWND m_myHandle;

};

// MyClassWindows.cpp implements methods for MyClassWindows

优点:

  • 更清晰的代码。

缺点:

  • 用户无法直接创建您的类的实例(特别是不在堆栈上)。
  • 您必须为创建提供特殊功能:例如声明IMyClass * createMyClass();并在MyClassWindows.cpp和其他特定于平台的文件中定义它。在那种情况下(好吧,实际上在整个多态性的情况下)你还应该定义破坏实例的函数 - 为了保持成语“创建它的人也应该销毁”。
  • 由于虚拟方法的影响有点放缓(在这些日子里,除了非常非常特殊的情况外几乎完全没有意义)。
  • 注意:由于RAM碎片问题,在内存有限的平台上分配可能会出现问题。在这种情况下,可以通过为对象使用某种内存池来解决它。

3。)PIMPL习语。

// MyClass.h

class MyClass
{
public:

    MyClass();
    void doSomething();

private:

    struct MyClassImplementation;

    MyClassImplementation *m_impl;

}


// MyClassWindows.h

#include <windows.h>
#include "MyClass.h"

struct MyClassImplementation
{
    HWND m_myHandle;

    void doSomething();

}

在这种情况下,MyClassImplementation保留所有需要的(至少是特定于平台的)数据并实现所需(同样,特定于平台)。在MyClass.cpp中,您可以在构造函数中包含特定于平台的实现(方法可以是内联的)(或者稍后如果您需要 - 请小心)您分配实现,并且在析构函数中您将销毁它。

优点:

  • 用户可以创建您的类的实例(包括堆栈)(不用担心未删除/删除的poiners)。
  • 您无需在MyClass.h中包含特定于平台的标头。
  • 您可以简单地添加引用计数器并实现数据共享和/或写入时复制,即使它保留大量数据,也可以轻松地将您的类用作返回值。

缺点:

  • 您必须分配实施对象。对象池可以提供帮助。
  • 调用方法时,会调用两个而一个指针解除引用。不过,今天不应该有任何问题。

4.。)定义一个中性类型,它足以保存您的数据。例如long long int。

// MyClass.h

class MyClass
{
public:

    MyClass();
    void doSomething();

private:

    typedef unsigned long long int MyData;

    MyData m_data;

};

在实现中(例如MyClassWindows.cpp),您总是需要在MyClass :: MyData和存储的实际数据之间进行转换(重新解释转换)。

优点:

  • 与第一种方法一样快但你避免使用宏。
  • 如果不需要,可以避免分配。
  • 避免多方法调用。
  • 避免在MyClass.h中包含特定于平台的标题。

缺点:

  • 您必须绝对110%确定MyClass :: MyData的大小始终至少与存储的数据相同。
  • 如果不同的平台存储不同大小的数据,那么除非使用宏,否则你会使用空间(如果你使用了几个项目,没关系,但有数百万个......)。在这种情况下,它不会那么混乱。
  • 这是数据的低级工作 - 不安全,而不是OOP。但很快。

所以使用最适合你问题的那个......以及你的个性:3因为今天的力量在速度和空间方面都差不多相对平等。

答案 1 :(得分:3)

这种方法的关键是,您将OS特定数据封装在特定于os的文件中。如果你必须通过HWND,那么你可能会重新考虑你的对象设计。这样的strcuture是否有意义取决于你的os特定代码有多大。你真的不想将所有可能的类压缩到一个文件中。

另一方面,有一些GUI正在执行此操作 - 将操作系统特定部分封装在库中,如QT或wxWidgets或其他。如果您正确地将GUI与主代码分开,那么您甚至可能不需要这种方法。

我在我的项目中使用这样的结构来支持不同版本的xerces,而不会使主代码与#ifdefs混乱。但在我的情况下,受影响的代码相当小。我可以想象GUI框架需要更多的空间。

答案 2 :(得分:2)

我只会在@ Laethnes&#39;中添加一个子弹。答案

5)编写一个空头,在编译时包含平台头,并在typedef下隐藏特定于平台的类

// MyClass.hpp

#if defined(WINDOWS)
#    include "WINMyClass.hpp"
     typedef WINMyClass MyClass
#elif defined(OSX)
#    include "OSXMyClass.hpp"
     typedef OSXMyClass MyClass

   ...  // keep going

#endif

优点:

  • 它只是一个typedef,非常简单
  • 良好的可读性
  • 速度

缺点:

  • 它只是一个typedef

答案 3 :(得分:0)

执行此操作的常用方法是使用多态。您提供了一个界面,可以在不考虑特定平台的情况下抽象您的功能:

class thread
{
    virtual ~thread() {}
    virtual void run() = 0;
    /* whatever */
};

然后您可以使用特定于平台的功能继承此类:

class posix_thread : thread;

在编译时,您使用#ifdef选择包含的类并进行实例化。

答案 4 :(得分:0)

大多数情况下,你会在标题中避免这样的事情。那里 但是,当你想要公开它们时。在这样的 例如,我的解决方案一直是使用类似的东西:

#include dependentInclude(syst,MyInclude.lhh)

dependentInclude是一个扩展到路径的宏 我需要的文件,取决于syst的值(已设置) 在命令行上)。