可以使用不同的实现文件来实现多态性吗



如果给定接口有多个所需的实现,但在编译时之前已知所需的具体实现,那么简单地将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命令行上提供定义,这取决于所选择的构建配置。

最新更新