【问题标题】:Cross-platform OOP in C++C++ 中的跨平台 OOP
【发布时间】:2010-12-25 10:53:39
【问题描述】:

当然,我知道最好的答案是“不要编写自己的跨平台代码,有人已经完成了你需要的工作”,但我这样做是出于兴趣/学习练习,而不是任何付费容量。基本上,我正在用 C++ 编写一个小型控制台应用程序,我想让它跨平台,处理文件、套接字和线程之类的东西。 OOP 似乎是处理这个问题的好方法,但我还没有真正找到一个好的模式来编写跨平台共享相同接口的类。

简单的方法是只规划一些元接口,在程序的其余部分使用它,然后根据平台用不同的文件编译同一个类,但我觉得必须有更好的方法那更优雅;至少,不会混淆 IntelliSense 及其同类的东西会很好。

我查看了 wxWidgets 源代码中的一些较小的类,它们使用了一种方法,该方法使用一个私有成员来保存该类的数据,例如

class Foo
{
    public:
        Foo();

        void Bar();
    private:
        FooData data;
};

然后,您可以通过根据平台选择不同的实现文件来编译它。这种方法对我来说似乎很笨拙。

我考虑过的另一种方法是编写一个接口,并根据平台替换从该接口继承的类。像这样的:

class Foo
{
    public:
        virtual ~Foo() {};
        virtual void Bar() = 0;
};

class Win32Foo
{
    public:
        Win32Foo();
        ~Win32Foo();
        void Bar();
};

当然,由于您不知道创建对象的实现方式,因此这种方式会搞砸实际的实例化,但这可以通过使用函数来解决

Foo* CreateFoo();

并根据您运行的平台改变函数的实现。我也不是这个的超级粉丝,因为用一堆实例化方法乱扔代码看起来仍然很笨重(这也与创建非跨平台对象的方法不一致)。

这两种方法哪一种更好?有没有更好的办法?

编辑:澄清一下,我的问题不是“你如何编写跨平台 C++?”相反,它是“使用 C++ 中的类抽象出跨平台代码的最佳方法是什么,同时尽可能多地从类型系统中受益?”

【问题讨论】:

    标签: c++ oop cross-platform


    【解决方案1】:

    要真正实现需要操作系统支持的跨平台功能(如套接字),您将不得不编写在某些平台上无法编译的代码。这意味着您的面向对象设计需要补充预处理器指令。

    但是由于无论如何您都必须使用预处理器,因此从socket 基类继承的win32socket(例如)之类的东西是否必要或有用是值得怀疑的。当在运行时多态地选择不同的函数时,OO 通常很有用。但跨平台功能通常更多的是编译时问题。 (例如,如果该函数甚至无法在 Linux 上编译,那么多态调用 win32socket::connect 是没有用的!)因此,最好简单地创建一个 socket 类,该类根据使用预处理器指令的平台而不同地实现。

    【讨论】:

    • 实际上没有必要用预处理器条件来乱扔代码 - 只需将特定于平台的代码放入单独的文件中,然后在 makefiles/build 脚本中进行选择。
    • 对;预处理器指令可以很简单,例如根据平台包含不同的头文件。
    • 当然——我的问题更多是关于如何为通用套接字类创建不同的实现。为那里的歧义道歉。
    • @ShZ,我明白这一点,但我要说的是,虚拟函数和一般的 OO 是一种运行时机制,因此对于设计跨平台的目的来说并不是真正有用或必要的代码。
    【解决方案2】:

    第二种方法更好,因为它允许您创建仅存在于其中一个平台上的成员函数。然后,您可以在平台无关代码中使用抽象类,在特定平台代码中使用具体类。

    例子:

    class Foo
    {
        public:
            virtual ~Foo() {};
            virtual void Bar() = 0;
    };   
    class Win32Foo : public Foo
    {
        public:
            void Bar();
            void CloseWindow(); // Win32-specific function
    };
    
    class Abc
    {
        public:
            virtual ~Abc() {};
            virtual void Xyz() = 0;
    };   
    class Win32Abc : public Abc
    {
        public:
            void Xyz()
            {
               // do something
               // and Close the window
               Win32Foo foo;
               foo.CloseWindow();
            }
    };
    

    【讨论】:

      【解决方案3】:

      理想情况下,您会将差异集中在可能的最低级别。
      所以你的顶级代码调用 Foo() 并且只有 Foo() 需要在内部关心它正在调用什么操作系统。

      ps。看看 'boost' 它包含很多东西来处理跨平台网络文件系统等

      【讨论】:

        【解决方案4】:

        定义你的接口,转发到detail 调用:

        #include "detail/foo.hpp"
        
        struct foo
        {
            void some_thing(void)
            {
                detail::some_thing();
            }
        }
        

        “detail/foo.hpp”类似于:

        namespace detail
        {
            void some_thing(void);
        }
        

        然后您将在detail/win32/foo.cppdetail/posix/foo.cpp 中实现它,并根据您为哪个平台编译,编译一个或另一个。

        通用接口只是将调用转发到特定于实现的实现。这类似于 boost 的做法。您需要查看 boost 以获取完整的详细信息。

        【讨论】:

        • 这是个好主意,我一定会看看 Boost 在那里做了什么。不过我很好奇,这种模式是否允许类状态在实现之间有所不同?例如,Posix 与 Win32 文件句柄具有不同的类型 - 如果我正在编写文件包装器,则状态结构/类必须不同。
        • 您通常会创建一个file_handle 类,然后将句柄塞入void*。实现可以将其转换回所需的内容。标准保证这是安全的。如果您动态加载实现,此方法也很有效,因为您只需将 detail::whatever 替换为从库加载的 some_function_pointer
        • +1:始终将特定于构建时的内容委托给构建系统
        【解决方案5】:

        在您的第一个示例或 GMan 中进行委托非常有效,特别是如果特定于平台的部分需要许多特定于平台的成员(例如,仅存在于一个平台中的类型的成员)。缺点是你必须维护两个类。

        如果类要更简单,没有很多特定于平台的成员,你可以只使用一个通用的类声明,并在两个不同的 .cc 文件中编写两个实现(可能加上第三个 .cc 用于平台无关的方法实现)。对于少数特定于平台的成员或具有特定于平台类型的成员,您可能需要在头文件中添加一些#ifdef。这种方式更像是一种黑客攻击,但更容易开始。如果它失控,您可以随时切换到代理。

        【讨论】:

          【解决方案6】:

          您无需使用 OOP 即可跨平台。跨平台更多的是一种架构和设计范式。

          我建议将所有平台特定代码与标准代码(例如套接字 GUI)隔离开来。在不同的平台上比较这些并编写一个通用的层或包装器。在您的代码中使用这一层。创建库函数以实现通用层的平台特定实现。

          平台特定的函数应该在单独的库或文件中。您的构建过程应该为特定平台使用正确的库。您的“标准”代码不应有任何更改。

          【讨论】:

            【解决方案7】:

            正如其他人所指出的,继承对于这项工作来说是一个错误的工具。在您的案例中使用的具体类型的决定是在构建时做出的,而经典的多态性使得该决定可以在运行时做出(即作为用户操作的结果)。

            【讨论】:

              【解决方案8】:

              您可以使用模板专业化来分隔不同平台的代码。

              enum platform {mac, lin, w32};
              
              template<platform p = mac>
              struct MyClass
              {
               // Platform dependent stuff for whatever platform you want to build for default here...
              };
              
              template<>
              struct MyClass<lin>
              {
               // Platform dependent stuff for Linux...
              };
              
              template<>
              struct MyClass<w32>
              {
               // Platform dependent stuff for Win32...
              };
              
              int main (void)
              {
               MyClass<> formac;
               MyClass<lin> forlin
               MyClass<w32> forw32;
               return 0;
              }
              

              【讨论】:

              猜你喜欢
              • 2011-04-14
              • 1970-01-01
              • 2011-07-11
              • 1970-01-01
              • 1970-01-01
              • 2019-06-16
              • 2014-09-28
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多