21

听说有一种写Cross Platform c++代码的方法是定义类如下(例如,一个Window类):

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

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

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

4

5 回答 5

39

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

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

优点:

  • 更清晰的代码。

缺点:

  • 用户不能直接创建您的类的实例(尤其是不在堆栈上)。
  • 您必须提供特殊的创建函数:例如 declare 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 中,您包含特定于平台的实现(方法可以是内联的),在构造函数中(或者稍后,如果您愿意 - 请注意)您分配实现,在析构函数中您将销毁它。

优点:

  • 用户可以创建您的类的实例(包括在堆栈上)(不用担心未/删除的指针)。
  • 您不需要在 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,因为就当今的力量而言,所有四个在速度和空间方面都或多或少相对相等。

于 2013-08-14T18:22:22.777 回答
10

我只会在@Laethnes 的答案中添加一个 5) 项目符号

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
于 2015-06-09T09:32:49.540 回答
3

这种方法的要点是,您将特定于操作系统的数据封装在特定于操作系统的文件中。如果您必须绕过 HWND,那么您可能会重新考虑您的对象设计。这种结构是否有意义取决于您的操作系统特定代码有多大。您真的不想将所有可能的类压缩到一个文件中。

另一方面,有一些用于 GUI 的库正是这样做的——将操作系统特定的部分封装在 QT 或 wxWidgets 等库中。如果您正确地将 GUI 与主代码分开,那么您甚至可能不需要这种方法。

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

于 2013-08-14T17:36:55.557 回答
0

一种常见的方法是使用多态性。您提供一个接口来抽象您的功能,而不考虑特定平台:

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

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

class posix_thread : thread;

在编译时,您可以选择#ifdef包含和实例化的类。

于 2013-08-14T17:38:44.773 回答
0

大多数情况下,您会避免在标题中出现此类内容。但是,有时您想暴露它们。在这种情况下,我的解决方案一直是使用类似的东西:

#include dependentInclude(syst,MyInclude.lhh)

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

于 2013-08-14T18:07:40.253 回答