代码之家  ›  专栏  ›  技术社区  ›  Michael J

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

  •  4
  • Michael J  · 技术社区  · 10 年前

    在给定接口有多个所需实现的情况下,但在编译时已知所需的具体实现,那么简单地将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 回复  |  直到 10 年前
        1
  •  3
  •   Community Mohan Dere    9 年前

    实施

    由于这是静态多态性,奇怪的递归模板模式可能比交换一个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 或者使用这些线程中讨论的方法- Generate include file name in a macro / Dynamic #include based on macro definition -生成 #包括 从同一个 -D 宏。

        2
  •  3
  •   eerorika    10 年前

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

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

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

        3
  •  0
  •   Otto V.    10 年前

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

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

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

    事实上,与其交换定义,不如让代码单独存在 使用 -D 构建开关, 这取决于所选择的构建配置。