是否可以使用不同的实现文件来实现多态?

时间:2016-04-18 13:22:52

标签: c++ inheritance polymorphism implementation

如果给定接口有多个所需的实现,但是在编译时需要特定的实现,那么将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是错误的吗?这样做的最佳方式是什么?

3 个答案:

答案 0 :(得分:3)

在编译时选择.cpp文件是可以的,并且完全合理... 如果忽略的.cpp文件将无法编译。这是选择特定于平台的实现的一种方法。

但总的来说 - 在可能的情况下(例如在你的例子中) - 最好使用模板来实现静态多态。如果需要在编译时进行选择,请使用预处理器宏。

如果两个实现引用了不应该相交的单独项目但仍然是给定接口的实现,我建议将该接口作为单独的“项目”提取”。这样,单独的项目彼此之间没有直接关系,即使它们都依赖于提供界面的第三个项目。

答案 1 :(得分:2)

实施

由于这是静态多态性,奇怪的重复模板模式可能比交换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();
}

您可以在单个标头和#include中声明所有Car变体,或者使用类似这些主题中讨论的方法 - Generate include file name in a macro / Dynamic #include based on macro definition - 来生成{{ 1}}来自同一个#include宏。

答案 2 :(得分:0)

在您的使用案例中,我认为最好使用 ifdef -blocks。这将在编译之前检查!对于相同的代码,此方法有时也用于区分不同平台。

{{1}}

在这些代码中,编译器将忽略fiat的 ifdef -block,因为只定义了FERRARI。这样你仍然可以使用你想要的两种方法。你想要的一切都不同,你可以放入ifdef并简单地换掉定义。

  

实际上,不是交换定义,而是单独留下代码   使用{{1}}构建开关在GCC命令行上提供定义,   取决于选择的构建配置。