跨平台库设计,最佳实践

4

如何设计具有不同平台特定实现的函数的最佳实践?

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

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

每个部分都包含大量代码(目前确实如此),这使得阅读等变得困难。
应该如何正确处理这种情况?

3
将不同的实现分离成特定的函数,并将它们在API函数中的调用用ifdefs区分开。 - πάντα ῥεῖ
如果我理解你的意思正确,@πάνταῥεῖ,你建议需要将每个实现提取到函数中并在保持ifdef结构不变的情况下进行调用,对吗? - HighPredator
最佳实践是首先了解你正在使用的编程语言,不要为非常不同的语言添加标签。 - too honest for this site
@πάνταῥεῖ 这是一个不错的方法,但我认为你可以通过启用/禁用函数的部分来获得更多的抽象化,就像帖子中所示。原因是,不同的操作系统可能需要完全不同的头文件,并且它们本身具有完全不同的API。因此,在函数外添加#ifdef将导致不必要的代码重复。 - jan.sende
1
@jan.sende 这些操作系统特定功能的粒度通常会有很大差异,试图找到和概括这些功能代码时,往往不值得冒险。这完全取决于具体的使用案例。 - πάντα ῥεῖ
3个回答

4
如果实现完全不同,最好不要使用预处理器条件,而是为每个平台有单独的 .c 文件。每个文件都包含同一共享头文件中声明的不同平台特定实现的函数。正确的文件将由构建系统选择。
例如,在 GLFW 中,有 x11_window.cwin32_window.c,它们都实现了相同的函数,如_glfwPlatformGetWindowSize()。

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的实例时,您的代码将针对您创建的引擎类型进行特定设置,但是无论实现方式的差异如何,您都可以重复使用相同的接口代码。


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;
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接