提问人:willdo 提问时间:8/17/2022 最后编辑:willdo 更新时间:8/18/2022 访问量:176
C++03 用模板元编程替换预处理器指令
C++03 Replace Preprocessor Directives with Template Metaprogramming
问:
我有一个嵌入式 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++ 模板的尝试。
由于在我的特定平台上定义错误,以下代码无法编译,因为 either 或 未定义取决于 的值。Test()
Foo()
Bar()
Type
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);
我可能想多了,我只是不想让现在糟糕的设计以后咬我。
答:
我同意代码看起来很复杂,我和你在一起。但我相信你走错了方向。模板看起来很酷,但在这种情况下,它们不是正确的工具。使用模板,您每次都会编译所有选项,即使它们没有被使用。
你想要的恰恰相反。您一次只想编译一个源代码。两全其美的正确方法是将每个实现分离到不同的文件中,然后使用外部方法选择要包含/编译的文件。
构建系统通常有很多工具可以用于这方面。例如,对于本地编译,我们可以依靠 CMAKE 自己的CMAKE_SYSTEM_PROCESSOR来识别哪个是当前的处理器。
如果要交叉编译,则需要指定要编译到的平台。
举个例子,我有一个软件需要在许多操作系统中编译,如 Redhat、CentOS、Ubuntu、Suse 和 Windows/Mingw。我有一个 bash 脚本文件,用于检查环境并加载特定于该操作系统的工具链 cmake 文件。
你的情况似乎更简单。您可以指定要使用的平台,并指示构建系统仅编译特定于该平台的文件。
评论
你:)到达那里。
重写函数,使其不依赖于全局 Gadget 对象,而是将一个对象作为模板化参数:Test
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
Foo
Bar
请注意,如果两者都被定义为两个重载将匹配,则此代码将中断。Foo
Bar
Gadget
如果你不能在类中包装对 Foo 和 Bar 的调用,这就会带来一个问题:Gadget
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);
评论