【问题标题】:Alternatives to preprocessor directives预处理器指令的替代方案
【发布时间】:2009-05-13 11:21:32
【问题描述】:

我从事在 Symbian 平台上开发 C++ 手机应用程序。其中一项要求是它必须适用于从第 2 版手机到第 5 版手机的所有 Symbian 手机。现在,不同版本的 Symbian SDK 存在差异。我必须使用预处理器指令有条件地编译与构建应用程序的 SDK 相关的代码,如下所示:

#ifdef S60_2nd_ED
  Code
#elif S60_3rd_ED
  Code
#else
  Code

现在,由于我正在开发的应用程序并非微不足道,它很快就会增长到数万行代码,并且像上面这样的预处理器指令将遍布各处。我想知道在这种情况下是否有任何替代方法或者可能是使用这些预处理器指令的更好方法。

请帮忙。

【问题讨论】:

    标签: c++ c-preprocessor


    【解决方案1】:

    嗯...这取决于差异的确切性质。如果可以将它们抽象出来并将它们隔离到特定的类中,那么您可以走这条路。这意味着拥有某些类的特定于版本的实现,并切换整个实现,而不是到处切换几行。

    你应该有

    • MyClass.h
    • MyClass_S60_2nd.cpp
    • MyClass_S60_3rd.cpp

    等等。您可以选择编译哪个 CPP 文件,方法是使用上面的 #ifdefs 将整个内部包装起来,或者我在构建级别(通过 Makefile 或其他方式)控制在为各种目标构建时包含哪些文件。

    根据更改的性质,这可能会更干净。

    【讨论】:

    • 完全是我的想法。多态性来模拟行为差异。您可以使用继承来实现多态性(这种方式在您的答案中提出),但也可以使用聚合,由 Traits 类促进。
    • 这是最好的方法。将构建系统用于其用途; #ifdefs 是不可维护代码的路径。
    【解决方案2】:

    我一直在你所在的地方。

    一个技巧是,即使您要在代码中设置条件,也不要打开 Symbian 版本。这使得将来难以添加对新版本的支持,或者难以为在某些方面不寻常的手机进行定制。相反,确定您依赖的实际属性是什么,围绕这些编写代码,然后包含一个头文件:

    #if S60_3rd_ED
        #define CAF_AGENT 1
        #define HTTP_FILE_UPLOAD 1
    #elif S60_2nd_ED
        #define CAF_AGENT 0
        #if S60_2nd_ED_FP2
            #define HTTP_FILE_UPLOAD 1
        #else
            #define HTTP_FILE_UPLOAD 0
        #endif
    #endif
    

    等等。显然,如果您愿意,您可以按功能而不是按版本对定义进行分组,每个配置具有完全不同的标头,或者任何适合您的方案。

    我们也定义了您继承自的 UI 类,因此 S60 和 UIQ 之间有一些共同的 UI 代码。事实上,由于产品是什么,我们没有太多与 UI 相关的代码,因此其中相当一部分是常见的。

    不过,正如其他人所说,最好将变量行为集中到类和函数中,并链接不同的版本。

    [根据评论进行编辑:

    我们非常努力地避免做任何依赖分辨率的事情 - 幸运的是,特定的应用程序并没有让这变得太困难,所以我们有限的 UI 非常通用。我们打开屏幕分辨率的主要功能是用于启动画面/背景图像等。我们有一个脚本来预处理构建文件,它将宽度和高度替换为文件名,splash_240x320.bmp 或其他。我们实际上是手动生成的图像,因为没有那么多不同的尺寸,而且图像也不会经常变化。相同的脚本生成了一个 .h 文件,其中包含生成文件生成中使用的大多数值的#defines。

    这是针对每个设备构建的:我们还有更多通用 SIS 文件,它们只是动态调整图像大小,但我们经常对安装大小有要求(ROM 有时非常有限,如果您的应用是基本设备图像),并且调整图像大小是降低它的一种方法。为了在 N92、Z8 等设备上支持屏幕旋转,我们仍然需要某些图像的纵向和横向版本,因为翻转纵横比的效果不如调整到相同或相似的比例...]

    【讨论】:

    • 你好,你能不能告诉我你处理不同手机屏幕分辨率的方法
    【解决方案3】:

    在我们公司,我们编写了很多跨平台代码(win32/ps3/xbox/等的游戏开发)。
    为了尽可能避免与平台相关的宏,我们通常使用以下几个技巧:

    • 将平台相关代码提取到平台抽象库中,在不同平台上具有相同接口,但实现不同;
    • 针对不同平台将代码拆分为不同的 .cpp 文件(例如:“pipe.h”、“pipe_common.cpp”、“pipe_linux.cpp”、“pipe_win32.cpp”...);
    • 使用宏和辅助函数来统一特定于平台的函数调用(例如:“#define usleep(X) Sleep((X)/1000u)”);
    • 使用跨平台的第三方库。

    【讨论】:

    • 与您的建议非常相似,您还可以添加特定于每个构建的包含搜索目录 - 因此像 #include "sound_constants.h" 这样的内容将找到该文件的几个排列之一。
    • 不错的提示!我有点害怕低级错误,例如不同编译单元中的结构声明不匹配(#include "" 语法允许在所有附加和标准包含目录中构建具有最高优先级的相对包含路径 ~_~`)。不过像这样的守卫:everfall.com/paste/id.php?xxd85sd9c38g -- in headers 可以解决这样的问题...
    【解决方案4】:

    如果可能,您可以尝试为所有平台定义一个通用接口。然后,实现每个平台的接口。

    使用预处理器指令选择正确的实现。

    这样,您将在代码中更少的地方使用平台选择指令(理想情况下,在一个地方,明确地在声明接口的头文件中)。

    这意味着类似:

    commoninterface.h /* declaring the common interface API. Platform identification preprocessor directives might be needed for things like common type definitions */
    platform1.c /*specific implementation*/
    platform2.c /*specific implementation*/
    

    【讨论】:

      【解决方案5】:

      看看SQLite。他们有同样的问题。他们将依赖于平台的东西移动到单独的文件中,并通过使用排除整个文件内容的预处理器指令来有效地编译只需要的东西。这是一种广泛使用的方法。

      【讨论】:

        【解决方案6】:

        不知道替代方案,但您可以做的是,为不同版本的操作系统使用不同的文件。例子

        #ifdef S60_2nd_ED

        #include "graphics2"

        #elif S60_3rd_ED

        #include "graphics3"

        #else

        #include "graphics"

        【讨论】:

          【解决方案7】:

          您可以像他们为 linux 内核中的程序集定义所做的那样。每个架构都有自己的目录(例如 asm-x86)。所有这些文件夹都聚集了相同的高级头文件,呈现相同的界面。配置内核时,会创建一个名为 asm 的链接,指向相应的 asm-arch 目录。这样,所有 C 文件都包含 .

          【讨论】:

            【解决方案8】:

            S60 2nd ed 和 3rd ed 应用程序之间存在一些不限于代码的差异:应用程序资源文件不同,图形格式和打包工具不同,mmp 文件在许多方面不同。

            根据我的经验,不要尝试过多地自动化它,而是为第 2 版和第 3 版提供单独的构建脚本。在代码级别,与拥有通用抽象 API 的类的区别在于,仅在极少数情况下使用标志。

            【讨论】:

            • +1。我们曾经使用 python 自动生成构建脚本以避免一些重复。基本上替换和条件完成了大部分工作——这些天也许 Django 模板值得一试。当然,构建链随后会使用 perl 将它们处理成 makefile。我被引导相信最终会涉及到一个编译器;-)
            【解决方案9】:

            您应该尽量避免通过代码传播#ifs。

            相当;在头文件中使用#if 来定义替代宏,然后在代码中使用单个宏。

            这种方法可以让你的代码稍微更易读。

            例子:

             Plop.h
             ======
            
             #if V1
             #define    MAKE_CALL(X,Y)    makeCallV1(X,Y)
             #elif V2
             #define    MAKE_CALL(X,Y)    makeCallV2("Plop",X,222,Y)
             ....
             #endif
            
            
             Plop.cpp
             ========
            
             if (pushPlop)
             {
                 MAKE_CALL(911,"Help");
             }
            

            为了帮助将这个拆分版本的特定代码转换为自己的函数,然后使用宏来激活如上所示的函数。您还可以将 SDK 的更改部分包装在您自己的类中,以尝试提供一致的接口,然后您的所有差异都在包装类中进行管理,从而使您的代码更整洁。

            【讨论】:

              猜你喜欢
              • 2021-11-21
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多