如果给定接口有多个所需的实现,但在编译时之前已知所需的具体实现,那么简单地将make文件指向同一头的不同实现文件是否错误?
例如,如果有一个定义汽车(car.h)的程序
// Car.h
class Car {
public:
string WhatCarAmI();
}
在构建时,我们知道我们希望它是法拉利还是菲亚特,并给出相应的文件:
// Ferrari.cpp
#include "Car.h"
string Car::WhatCarAmI() { return "Ferrari"; }
而对于另一种情况(不足为奇)
// Fiat.cpp
#include "Car.h"
string Car::WhatCarAmI() { return "Fiat"; }
现在,我知道我可以制作菲亚特和法拉利衍生的Car对象,并在运行时选择我想要构建的对象。类似地,我可以将其模板化,并让编译器在编译时选择要构建的。然而,在这种情况下,这两个实现都引用了不应该相交的单独项目。
考虑到这一点,按照我的建议,简单地在给定项目的makefile中选择正确的.cpp是错误的吗?最好的方法是什么?
实现
由于这是一种静态多态性,因此Curioly Recurring Template Pattern可能比交换cpp文件更为惯用——这似乎很棘手。如果您想让多个实现共存于一个项目中,同时易于与强制的单个实现构建系统一起使用,那么CRTP似乎是必需的。我想说,它有据可查的性质和同时做到这两件事的能力(因为你永远不知道以后会需要什么)给了它优势。
简而言之,CRTP看起来有点像这样:
template<typename T_Derived>
class Car {
public:
std::string getName() const
{
// compile-time cast to derived - trivially inlined
return static_cast<T_Derived const *>(this)->getName();
}
// and same for other functions...
int getResult()
{
return static_cast<T_Derived *>(this)->getResult();
}
void playSoundEffect()
{
static_cast<T_Derived *>(this)->playSoundEffect();
}
};
class Fiat: public Car<Fiat> {
public:
// Shadow the base's function, which calls this:
std::string getName() const
{
return "Fiat";
}
int getResult()
{
// Do cool stuff in your car
return 42;
}
void playSoundEffect()
{
std::cout << "varooooooom" << std::endl;
}
};
(我以前用d_
作为派生实现函数的前缀,但我不确定这是否有任何好处;事实上,它可能会增加歧义…)
要了解CRTP中到底发生了什么-一旦你得到它就很简单!-周围有很多导游。你可能会发现这方面有很多变化,并选择你最喜欢的一个。
实施的编译时间选择
回到另一个方面,如果你确实想在编译时限制其中一个实现,那么你可以使用一些预处理器宏来强制执行派生类型,例如:
g++ -DMY_CAR_TYPE=Fiat
以及后来的
// #include "see_below.hpp"
#include <iostream>
int main(int, char**)
{
Car<MY_CAR_TYPE> myCar;
// Do stuff with your car
std::cout << myCar.getName();
myCar.playSoundEffect();
return myCar.getResult();
}
您可以在单个标头中声明所有Car变体和#include
,也可以使用类似于这些线程中讨论的方法-在宏中生成include文件名/Dynamic#include基于宏定义-从同一个-D
宏生成#include
。
在编译时选择.cpp文件是可以的,而且非常合理如果忽略的.cpp文件不会编译。这是选择特定于平台的实现的一种方法。
但一般来说,在可能的情况下(例如在您的琐碎示例中),最好使用模板来实现静态多态性。如果需要在编译时进行选择,请使用预处理器宏。
如果这两个实现引用的是单独的项目,它们不应该相交,但对于给定的接口仍然是实现,我建议将该接口提取为单独的"项目"。这样,单独的项目就不会直接相互关联,即使它们都依赖于提供接口的第三个项目。
在您的用例中,我认为最好使用ifdef-块。编译前将对此进行检查!这种方法有时也用于区分同一代码的不同平台。
// Car.cpp
#include "Car.h"
#define FERRARI
//#define FIAT
#ifdef FERRARI
string Car::WhatCarAmI() { return "Ferrari"; }
#endif
#ifdef FIAT
string Car::WhatCarAmI() { return "Fiat"; }
#endif
在这些代码中,编译器将忽略fiat的ifdef-块,因为只定义了FERRARI。这样,你仍然可以使用你想要的两辆车的方法。所有你想要的东西都不一样,你可以放入ifdefs,然后简单地交换定义。
实际上,与其交换定义,不如让代码单独使用使用CCD_ 5构建交换机在GCC命令行上提供定义,这取决于所选择的构建配置。