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

Is it okay to use different implementation files to achieve polymorphism?

如果给定接口有多个所需的实现,但在编译时之前已知所需的具体实现,那么简单地将 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 的 Fiat 和 Ferrari 派生对象,并在运行时选择我想要构建的对象。同样,我可以将其模板化并让编译器在编译时选择要构建的内容。但是,在这种情况下,这两个实现都引用了不应该相交的单独项目。

鉴于此,按照我的建议简单地 select 给定项目的 makefile 中的正确 .cpp 是否错误?最好的方法是什么?

实施

因为这是静态多态性,奇怪的重复模板模式可能比交换 cpp 文件更惯用——这看起来很老套。如果您想让多个实现在一个项目中共存,同时又易于与强制 single-implementation 构建系统一起使用,那么 CRTP 似乎是必需的。我会说它的 well-documented 性质和兼顾两者的能力(因为你永远不知道以后需要什么)给了它优势。

简而言之,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 中真正发生的事情 - 一旦掌握就很简单! - 周围有很多指南。您可能会发现很多变体,然后选择您最喜欢的那个。

Compile-time 选择实现

回到另一方面,如果您确实想限制在 compile-time 的实现之一,那么您可以使用一些预处理器宏来强制执行派生类型,例如:

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();
}

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

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

但一般来说 - 如果可能(例如在你的简单示例中) - 最好使用模板来实现静态多态性。如果您需要在编译时做出选择,请使用预处理器宏。

如果这两个实现 指的是不应该交叉的独立项目 但仍然是给定接口的 实现 ,我建议将该接口提取为单独的 "project"。这样,单独的项目就不会彼此直接相关,即使它们都依赖于提供接口的第三个项目。

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

// 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

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

Actually instead of swapping out the defines, you'd leave your code alone and provide the definitions on the GCC command line using the -D build switch, depending on what build configuration were selected.