抽象跨平台实现的常见习语是什么

What is a common idiom to abstract cross-platform implementations?

本文关键字:是什么 常见习 跨平台 实现 抽象      更新时间:2023-10-16

我正在编写一个旨在跨平台的程序;因此,它将支持某些操作的多种实现。我的第一个想法是编写一个类的层次结构,有一个通用的接口,也许是每个平台的抽象工厂。

class Operation {
    DoOperation() = 0;
}
class OperationPlatform1 : public Operation {
    DoOperation();
}
class OperationPlatform2 : public Operation {
    DoOperation();
}
#ifdef USING_PLATFORM1
    Operation *op = new OperationPlatform1;
#endif

但是,我意识到将使用的实现在编译时是已知的。我试图思考如何使用静态多态性来实现这一点,之后我意识到我也可以按照这些思路编写一些东西:

class OperationPlatform1 {
    DoOperation();
}
class OperationPlatform2 {
    DoOperation();
}
#ifdef USING_PLATFORM1
typedef OperationPlatform1 Operation;
#endif
Operation op;

抽象多个实现的好方法是什么,在编译时只会选择其中一个实现?我也对性能感兴趣,所以除非必要,否则我不想使用虚拟方法。

通常的解决方案是只定义一个类标头,并为其提供不同的源文件,编译并在必要时链接。 如果你需要一些类定义中的依赖关系(例如数据类型),您还可以为此,通过为每个平台,并包括它。 这里最简单的解决方案是可能为每个目标平台创建一个目录,并把所有那里的平台相关文件,使用 -I//I 进行选择向上正确的目录(从而正确的平台)依赖文件)在编译时。

过度使用#ifdef通常是设计不佳的标志。 看https://www.usenix.org/legacy/publications/library/proceedings/sa92/spencer.pdf。

您有几个选择:

  1. 使用问题中概述的方法。但是,您可能需要将平台特定的位拆分为单独的文件,并使用构建规则为平台引入正确的文件。

  2. 只需有一个类,并使用 #ifdef 为相应的平台插入正确的代码。

  3. 使用预先存在的包装器库(例如 Boost),它可能已经包装了平台特定的位,并针对包装器编写代码。

如果你要封装各种平台,那么你需要确保抽象不会泄漏到你的实现中。通常,您最终会使用列出的三个项目的组合。

你在这里拥有的是常用的。如果你绝对知道你永远不会同时有多个实现,你可以使用单个头文件,或者有条件编译的实现文件(cpp)或有平台预编译器指令从编译中排除某些代码(其他平台)。

您可以在标题中包含:

class Operation {
    DoOperation();
}

多个 cpp 您可以从构建中排除文件:

平台1.cpp

Operation::DoOperation() { // Platform 1 implementation }

和平台2.cpp

Operation::DoOperation() { // Platform 2 implementation }



单个实现文件

#ifdef PLATFORM1
    Operation::DoOperation() { // Platform 1 implementation }
elseif defined(PLATFORM2)
    Operation::DoOperation() { // Platform 2 implementation }
#endif



或者混合使用 2 个,如果你不想弄乱从构建中排除文件:

平台1.cpp:

#ifdef PLATFORM1
    Operation::DoOperation() { // Platform 1 implementation }
#endif

和平台 2.cpp:

#ifdef PLATFORM2
    Operation::DoOperation() { // Platform 2 implementation }
#endif

你想要的是提供对不同平台的支持,同时保持源代码干净。因此,您应该像您的第一个想法一样将平台依赖项抽象到某个接口中。

同时,您可能希望使用 #ifdef 来实现。例如,文件系统功能在很大程度上依赖于平台,因此您可以像这样定义文件系统接口。

class IFileSystem {
public:
    void CopyFile(const string& destName, const string& sourceName);
    void DeleteFile(const string& name);
    // etc.
};

然后你可以实现WindowsFileSystem,LinuxFileSystem,SolarisFileSystem等。

现在,当您的源代码需要文件系统功能时,您将访问请求的接口,如下所示:

IFileSystem& GetFileSystem() {
   #ifdef WINDOWS
       return WindowsFileSystemInstance;
   #endif
   #ifdef LINUX
       return LinuxFileSystemInstance;
   #endif
   // etc.
}
GetFileSystem().CopyFile("dest", "source");

此方法可帮助您分离应用程序逻辑和平台依赖项的关注点。

getter 函数的另一个好处是,您仍然可以为文件系统实现进行运行时选择,例如 Fat32FileSystem、NtfsFileSystem、Ext3File System 等。

看看现有的多平台框架,例如Qt。使用痘痘成语