[英]C++03 Replace Preprocessor Directives with Template Metaprogramming
I have a embedded C++03 codebase that needs to support different vendors of gadgets, but only ever one at a time.我有一个嵌入式 C++03 代码库,需要支持不同供应商的小工具,但一次只能支持一个。 Most of the functions overlap between the several gadgets, but there are a few exclusives, and these exclusive functions are creating a problem that I need to solve.
几个小工具之间的大部分功能重叠,但也有一些独占,而这些独占功能正在产生我需要解决的问题。
Here is an example of clumsy code that works using pre-processor conditionals:下面是一个使用预处理器条件的笨拙代码示例:
#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());}
Here is my attempt at converting the above code to C++ templates without pre-processor directives.这是我在没有预处理器指令的情况下将上述代码转换为 C++ 模板的尝试。 The following code does not compile due to an error in the definition of
Test()
on my particular platform, because either Foo()
or Bar()
is undefined depending on the value of Type
.由于我的特定平台上
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());}
I want to do this with templates to keep the number of code changes down when a new type or additional functions are added.我想使用模板来做到这一点,以在添加新类型或附加功能时减少代码更改的数量。 There are currently five types with at least two more expected soon.
目前有五种类型,预计很快还会再增加两种。 The pre-processor implementation code reeks, I want to clean this up before it gets unwieldy.
预处理器实现代码很臭,我想在它变得笨拙之前清理它。
The gadget code is a small amount of the total code base, so breaking up the entire project per gadget may not be ideal either.小工具代码只占总代码库的一小部分,因此按小工具拆分整个项目可能也不理想。
Even though only one type will ever be used for each project, the unused types still have to compile, how do I best design this using C++03 (no constexpr, const if, etc)?即使每个项目只会使用一种类型,但未使用的类型仍然必须编译,我如何最好地使用 C++03 进行设计(无 constexpr、const if 等)? Am I completely approaching this wrongly?
我完全错误地接近这个吗? I am willing to do a complete overhaul.
我愿意进行彻底的检修。
EDIT: Tomek's solution below makes me wonder if it violates LSP.编辑: Tomek 下面的解决方案让我想知道它是否违反了 LSP。 Effectively, another way to look at this is having
Test()
be part of an interface that requires implementation.实际上,另一种看待这个问题的方法是让
Test()
成为需要实现的接口的一部分。 So, the example can be reconsidered like the following:所以,这个例子可以重新考虑如下:
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?
};
Or similarly with the edited example:或与编辑后的示例类似:
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);
I might be over-thinking this, I just don't want a poor design now to bite me later.我可能想多了,我只是不想现在糟糕的设计以后再咬我。
I agree the code looks convoluted, I'm with you there.我同意代码看起来很复杂,我和你在一起。 But I believe you are going in the wrong direction.
但我相信你走错了方向。 Templates seem cool but they are not the right tool in this case.
模板看起来很酷,但在这种情况下它们不是正确的工具。 With templates you WILL always compile all the options every time, even if they are not used.
使用模板,您将始终编译所有选项,即使它们没有被使用。
You want the opposite.你想要相反的。 You want to ONLY compile one source at a time.
您一次只想编译一个源代码。 The proper way to have the best of both worlds is to separate each implementation in a different file and then pick which file to include/compile using external methods.
两全其美的正确方法是将每个实现分隔在不同的文件中,然后使用外部方法选择要包含/编译的文件。
Build systems usually have plenty of tools for this respect.构建系统通常有很多用于这方面的工具。 For example, for compiling natively, we can rely on CMAKE's own CMAKE_SYSTEM_PROCESSOR to identify which is the current processor.
例如,对于原生编译,我们可以依靠 CMAKE 自己的CMAKE_SYSTEM_PROCESSOR来识别哪个是当前处理器。
If you want to cross compile you need to specify which platform you want to compile to.如果要交叉编译,则需要指定要编译到的平台。
Case in mind, I have a software that needs to be compiled in many operating systems like Redhat, CentOS, Ubuntu, Suse and Windows/Mingw.请记住,我有一个软件需要在 Redhat、CentOS、Ubuntu、Suse 和 Windows/Mingw 等许多操作系统中编译。 I have one bash script file that checks for the environment and loads a toolchain cmake file specific for that operating system.
我有一个 bash 脚本文件,用于检查环境并加载特定于该操作系统的工具链 cmake 文件。
Your case seems to be even simpler.你的情况似乎更简单。 You could just indicate which platform you'd like to use and instruct the build system to compile just the file specific to that platform.
您可以只指出您想使用哪个平台,并指示构建系统只编译特定于该平台的文件。
You are getting there:).你到了那里:)。
Rewrite your Test
function so it doesn't rely on global Gadget object but instead takes one as a templated parameter:重写您的
Test
function 使其不依赖于全局小工具 object 而是将一个作为模板参数:
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());
}
And call it as:并将其称为:
Test(A);
This relies on SFINAE (Substitution Failure Is Not An Error) idiom.这依赖于 SFINAE(替代失败不是错误)习语。 Based on the definitions compiler will deduce the type of
T
to be a Gadget
.根据定义,编译器将
T
的类型推断为Gadget
。 Now, depending on availability of the Foo
and Bar
function it will pick one of the overloads.现在,根据
Foo
和Bar
function 的可用性,它将选择其中一个重载。
Please note this code WILL BREAK if both Foo
and Bar
are defined in Gadget
as the two overloads will match.请注意,如果
Foo
和Bar
都在Gadget
中定义,则此代码将中断,因为这两个重载将匹配。
This brings a question if you just can't wrap calls to Foo and Bar inside a Gadget
class:如果您不能在
Gadget
class 中包装对 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();}
};
and consistently call A.Test()
instead?并始终调用
A.Test()
代替?
EDIT:编辑:
I might have over-complicated it.我可能过于复杂了。 The following overload may be an easier approach to this:
以下重载可能是一种更简单的方法:
bool Test(Gadget<eType1> &o)
{
return(o.Foo());
}
bool Test(Gadget<eType2> &o)
{
return(o.Bar());
}
Test(A);
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.