【问题标题】:C++03 Replace Preprocessor Directives with Template MetaprogrammingC++03 用模板元编程替换预处理器指令
【发布时间】:2022-08-18 23:06:24
【问题描述】:

我有一个嵌入式 C++03 代码库,需要支持不同供应商的小工具,但一次只能支持一个。几个小工具之间的大部分功能重叠,但也有一些独占功能,而这些独占功能正在产生我需要解决的问题。

下面是一个使用预处理器条件的笨拙代码示例:

#define HW_TYPE1    0
#define HW_TYPE2    1

#define HW_TYPE HW_TYPE1

struct  GadgetBase  {
    void    FncA();
    // Many common methods and functions
    void    FncZ();
};
#if HW_TYPE==HW_TYPE2
struct  Gadget  :   public  GadgetBase  {
    bool    Bar()       {return(true);}
};
#else
struct  Gadget  :   public  GadgetBase  {
    bool    Foo()       {return(false);}
};
#endif

Gadget  A;

#if HW_TYPE==HW_TYPE2
bool    Test()  {return(A.Bar());}
#else
bool    Test()  {return(A.Foo());}

这是我在没有预处理器指令的情况下将上述代码转换为 C++ 模板的尝试。 由于在我的特定平台上Test() 的定义出错,以下代码无法编译,因为根据Type 的值,Foo()Bar() 未定义。

enum    TypeE   {
    eType1,
    eType2
};

const TypeE Type= eType1; // Set Global Type

// Common functions for both Gadgets
struct  GadgetBase  {
    void    FncA();
    // Many common methods and functions
    void    FncZ();
};

// Unique functions for each gadget
template<TypeE  E=  eType1>
struct  Gadget  :   public  GadgetBase          {
    bool    Foo()       {return(false);}
};
template<>
struct  Gadget<eType2>  :   public  GadgetBase  {
    bool    Bar()       {return(true);}
};

Gadget<Type>    A;
template<TypeE  E=  eType1>
bool    Test()  {return(A.Foo());}
template<>
bool    Test()  {return(A.Bar());}

我想使用模板来做到这一点,以在添加新类型或附加功能时减少代码更改的数量。目前有五种类型,预计很快还会再增加两种。预处理器实现代码很臭,我想在它变得笨拙之前清理它。

小工具代码仅占总代码库的一小部分,因此按小工具拆分整个项目可能也不理想。

即使每个项目只会使用一种类型,但未使用的类型仍然必须编译,我如何使用 C++03 最好地设计它(没有 constexpr、const if 等)?我完全错误地接近这个吗?我愿意进行彻底的检修。

编辑:Tomek 下面的解决方案让我怀疑它是否违反了 LSP。实际上,另一种看待这个问题的方法是让Test() 成为需要实现的接口的一部分。所以,这个例子可以重新考虑如下:

struct  GadgetI    {
    virtual bool Test()=0;
};
template<TypeE  E=  eType1>
struct  Gadget  :   public  GadgetBase, public GadgetI  {
    bool    Foo()       {return(false);}
    bool    Test()      {return Foo();}
};
template<>
struct  Gadget<eType2>  :   public  GadgetBase, public GadgetI  {
    bool    Bar()       {return(true);}
    bool    Test()      {return Bar();}
};
template<>
struct  Gadget<eType3>  :   public  GadgetBase, public GadgetI  {
    bool    Test()      {} // Violation of LSP?
};

或与编辑后的示例类似:

template<typename T>
bool Test(T& o)              {}  // Violation?
template<>
bool Test(Gadget<eType1> &o) {return(o.Foo());}
template<>
bool Test(Gadget<eType2> &o) {return(o.Bar());}

Test(A);

我可能想多了,我只是不想现在糟糕的设计以后再咬我。

    标签: c++ preprocessor template-specialization c++03


    【解决方案1】:

    我同意代码看起来很复杂,我和你在一起。但我相信你走错了方向。模板看起来很酷,但在这种情况下它们不是正确的工具。使用模板,您将始终编译所有选项,即使它们没有被使用。

    你想要相反的。您一次只想编译一个源代码。两全其美的正确方法是将每个实现分隔在不同的文件中,然后使用外部方法选择要包含/编译的文件。

    构建系统通常有很多用于这方面的工具。例如,对于原生编译,我们可以依靠 CMAKE 自己的CMAKE_SYSTEM_PROCESSOR 来识别哪个是当前处理器。

    如果你想cross compile 你需要指定你想编译到哪个平台。

    请记住,我有一个软件需要在 Redhat、CentOS、Ubuntu、Suse 和 Windows/Mingw 等许多操作系统中编译。我有一个 bash 脚本文件,用于检查环境并加载特定于该操作系统的 toolchain cmake file

    你的情况似乎更简单。您可以只指出您想使用哪个平台,并指示构建系统只编译特定于该平台的文件。

    【讨论】:

    • MadFred,感谢您的回复。我相信您是在建议我为每个独特的小工具功能创建一个新的实现文件,并为每个构建使用不同的工具链命令。你能帮我理解这与我上面的预处理器(PP)解决方案有什么不同吗?但不是条件指令,它只是将代码放在不同的翻译单元中。实际上,我们只是以不同的方式向编译器隐藏代码,对吧?这比 PP 解决方案更可取吗?
    • @willdo 当然,这与预处理器非常相似,因为只有需要的代码才会提交给编译器。但是,它是一种更清洁的解决方案。您可以在 C 库和 Linux 内核中看到这一点,它们在每个平台都有文件夹。但这都是规模问题。如果您在几个变体中只有几个类,请使用预处理器。但是如果你想支持少数或更多,代码变得太令人眼花缭乱而无法处理,最好拆分成文件。
    【解决方案2】:

    你到了那里:)。

    重写您的 Test 函数,使其不依赖于全局 Gadget 对象,而是将一个作为模板参数:

    template<class T>
    bool Test(T &o, std::integral_constant<bool (T::*)(), &T::Foo> * = 0)
    {
      return(o.Foo());
    }
    
    template<class T>
    bool Test(T &o, std::integral_constant<bool (T::*)(), &T::Bar> * = 0)
    {
      return(o.Bar());
    }
    

    并将其称为:

    Test(A);
    

    这依赖于 SFINAE(替代失败不是错误)习语。根据定义,编译器将推断T 的类型为Gadget。现在,根据FooBar 函数的可用性,它将选择其中一个重载。

    请注意,如果FooBar 都在Gadget 中定义,则此代码将中断,因为这两个重载将匹配。

    如果您不能在 Gadget 类中包装对 Foo 和 Bar 的调用,这会带来一个问题:

    template<TypeE  E=  eType1>
    struct  Gadget  :   public  GadgetBase          {
        bool    Foo()       {return(false);}
        bool    Test()      {return Foo();}
    };
    template<>
    struct  Gadget<eType2>  :   public  GadgetBase  {
        bool    Bar()       {return(true);}
        bool    Test()      {return Bar();}
    };
    

    并始终致电A.Test()

    编辑:

    我可能过于复杂了。以下重载可能是一种更简单的方法:

    bool Test(Gadget<eType1> &o)
    {
      return(o.Foo());
    }
    
    bool Test(Gadget<eType2> &o)
    {
      return(o.Bar());
    }
    
    Test(A);
    

    【讨论】:

    • 托梅克,感谢您的回复。两个厘米。 1.我相信integral_constant是在C++11中引入的。 2.您的解决方案似乎有点像访客模式。太棒了。我对 EDITED 解决方案的问题是:如果我有第三个不需要`Test() 的小工具怎么办?如果我为第三个小工具做一个空的测试,代码不会开始有味道吗?
    • 添加一个空的、包罗万象的模板,它什么都不做。即使在 C++03 上,integral_constant 也很容易实现。
    • 谢谢。我编辑了上面的 OP 以反映这一贡献,但我有一个关于可能违反 LSP 的问题。如果我遇到 C++03 的限制,那很好。就是这样。
    猜你喜欢
    • 2021-11-21
    • 2012-08-14
    • 1970-01-01
    • 2023-01-21
    • 1970-01-01
    • 2011-02-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多