多平台库设计。最佳实践

时间:2018-08-16 09:05:59

标签: c++ c cross-platform

设计具有不同平台特定实现的功能的最佳实践是什么?

例如,我在库模块中有一个结构看起来像这样的函数(已导出):

void foo()
{
#ifdef PLATFORM_WINDOWS
  // windows-specific implementation
#elif PLATFORM_LINUX
  // linux-specific implementation
#elif PLATFORM_SOLARIS
  // solaris-specific implementation
#endif
}

每个部分都可以(并且确实有atm)包含很多代码,这使得它很难阅读等等。

做这样的事情的正确方法是什么?

3 个答案:

答案 0 :(得分:3)

如果实现方式完全不同,则最好不要使用预处理器条件,而是为每个平台使用单独的.c文件。每个都将包含在共享头文件中声明的相同功能的不同平台特定实现。然后,构建系统将选择正确的文件。

例如在GLFW中,有x11_window.cwin32_window.c,并且都实现相同的功能,例如_glfwPlatformGetWindowSize()。

答案 1 :(得分:3)

在C ++中执行此操作的一种流行方法是使用接口抽象出实现细节。 您可以使用它们来创建独立于平台的代码,这也确实有助于处理不再支持API的情况。

例如,假设您有一个想要使用DirectX或OpenGL的引擎,您的类可能看起来像这样。

class IEngine 
{
   public:
   virtual void InitEngine() = 0;
};

class OpenGLEngine : public IEngine
{
   public:
   void InitEngine() override
   {
        //OpenGL specific implementation here
   }
}

然后,当您初始化IEngine实例时,您的代码将特定于您创建的任何类型的引擎,但是无论实现上的差异如何,您都可以重用相同的接口代码。

答案 2 :(得分:1)

我从C ++的角度答复。 C的答案将大不相同,我对此没有提供任何建议的信心。有些原则可以很好地翻译,但有些则不能。

我建议您将特定于平台的代码隐藏在界面后面。在界面内部,提供一个静态函数以返回指向您的API的指针,但不要在与平台无关的代码中对其进行定义。

然后在平台特定的单独文件中创建从该接口继承的不同类。 在特定于平台的.cpp文件中,您提供了在接口中声明的静态函数的定义。

对于不适当的平台,我建议您从构建脚本中完全排除平台特定的文件。失败的话,您应该将它们的整体包装在适当的ifdef子句中,但是很容易犯错并且不太可靠。

请注意,在这种情况下,在函数内部执行的计算可能对性能至关重要-可以。限制是由于虚函数调用,此类函数不应在紧密的循环内调用。

如果您确实需要压缩每一盎司的性能,则可以摆脱该接口,松开它提供的安全性和美观性,并仅在不同的.h / .cpp文件中实现相同的功能。如果您使用C编写,这是您最有可能做的事情-但是,我还是希望一些C专家对此发表意见。

一个最小的示例如下所示:

  

MyPlatformSpecificAPI.h

class MyPlatformSpecificAPI
{
    public:
        virtual ~MyPlatformSpecificAPI() = default; //Don't forget a virtual destructor 
        static MyPlatformSpecificAPI* getPlatformSpecificAPI(); //Notice, no implementation
        virtual uint8_t myPlatformSpecificFoo(uint32_t bar) = 0;


        //Because we're declaring an explicit destructor, explicitly default the 4 special member functions, check Rule of Five
        MyPlatformSpecificAPI(const MyPlatformSpecificAPI&) = default;
        MyPlatformSpecificAPI(MyPlatformSpecificAPI&&) = default;
        MyPlatformSpecificAPI& operator=(const MyPlatformSpecificAPI&) = default;
        MyPlatformSpecificAPI& operator=(MyPlatformSpecificAPI&&) = default;
};
  

MyPlatformSpecificAPI_Windows.h

#include "MyPlatformSpecificAPI.h"
class MyPlatformSpecificAPI_WIN64 : public MyPlatformSpecificAPI
{
public:
    virtual uint8_t myPlatformSpecificFoo(uint32_t bar) override;
    static MyPlatformSpecificAPI_WIN64 s_API;
};
  

MyPlatformSpecificAPI_Windows.cpp

uint8_t MyPlatformSpecificAPI_WIN64::myPlatformSpecificFoo(uint32_t bar)
{
    //Perform windows specific calculations
    return 42; //because 42 is always the answer
}

MyPlatformSpecificAPI* MyPlatformSpecificAPI::getPlatformSpecificAPI()
{
    return &MyPlatformSpecificAPI_WIN64::s_API;
}
  

MyPlatformSpecificAPI_Xbox.h

#include "MyPlatformSpecificAPI.h"
class MyPlatformSpecificAPI_Xbox : public MyPlatformSpecificAPI
{
public:
    virtual uint8_t myPlatformSpecificFoo(uint32_t bar) override;
    static MyPlatformSpecificAPI_Xbox s_API;
}
  

MyPlatformSpecificAPI_Xbox.cpp

uint8_t MyPlatformSpecificAPI_Xbox::myPlatformSpecificFoo(uint32_t bar)
{
    //Perform Xbox specific calculations
    return 84;
};

MyPlatformSpecificAPI* MyPlatformSpecificAPI::getPlatformSpecificAPI()
{
    return &MyPlatformSpecificAPI_Xbox::s_API;
}