4

In the case where there are multiple desired implementations for a given interface, but where the specific implementation desired is known before compile time, is it wrong simply to direct the make file to different implementation files for the same header?

For example, if have a program defining a car (Car.h)

// Car.h
class Car {
  public: 
    string WhatCarAmI();
}

and at build time we know whether we want it to be a Ferrari or a Fiat, to give each either of the corresponding files:

// Ferrari.cpp
#include "Car.h"
string Car::WhatCarAmI() { return "Ferrari"; }

whilst for the other case (unsurprisingly)

// Fiat.cpp
#include "Car.h"
string Car::WhatCarAmI() { return "Fiat"; }

Now, I am aware that I could make both Fiat and Ferrari derived objects of Car and at runtime pick which I would like to build. Similarly, I could templatize it and make the compiler pick at compile time which to build. However, in this case the two implementations both refer to separate projects which should never intersect.

Given that, is it wrong to do what I propose and simply to select the correct .cpp in the makefile for the given project? What is the best way to do this?

sepp2k
  • 363,768
  • 54
  • 674
  • 675
Michael J
  • 43
  • 4
  • You can do that, yes. That's a bit of a poor version of the [Pimpl Idiom](https://en.wikibooks.org/wiki/C%2B%2B_Programming/Idioms#Pointer_To_Implementation_.28pImpl.29). – πάντα ῥεῖ Apr 18 '16 at 13:26
  • 3
    Since this is static polymorphism, using CRTP (`class Fiat: public Car`) is probably vastly more idiomatic than swapping out a cpp file, and will be absolutely required if you want multiple interfaces to coexist. – underscore_d Apr 18 '16 at 13:26
  • 2
    You could, but you shouldn't. That's a pretty poor way of achieving polymorphism, and a pretty poor design choice altogether. – Nico Apr 18 '16 at 13:28
  • 1
    For things known at compile time, template's are the way to go. You can use CRTP like @underscore_d said, or you can look up "policy based design" which will also allow you to achieve compile-time polymorphism. – RyanP Apr 18 '16 at 13:32
  • I don't know how much you can consider it polymorphism, there are no multiple types with the same interface here. There's a different implementation for the same type which changes because the code changes, which is somewhat different. – Jack Apr 18 '16 at 13:35
  • @Jack True, and nor is there inheritance, yet both are tagged ;-) I was inclined to edit and remove these but might let the OP think about that first. – underscore_d Apr 19 '16 at 09:56

3 Answers3

3

Implementation

As this is static polymorphism, the Curiously Recurring Template Pattern is probably vastly more idiomatic than swapping a cpp file - which seems pretty hacky. CRTP seems to be required if you want to let multiple implementations coexist within one project, while being easy to use with an enforced single-implementation build system. I'd say its well-documented nature and ability to do both (since you never know what you'll need later) give it the edge.

In brief, CRTP looks a little like this:

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

(I've previously prefixed derived implementation functions with d_, but I'm not sure this gains anything; in fact, it probably increases ambiguity...)

To understand what's really going on in the CRTP - it's simple once you get it! - there are plenty of guides around. You'll probably find many variations on this, and pick the one you like best.

Compile-time selection of implementation

To get back to the other aspect, if you do want to restrict to one of the implementations at compile-time, then you could use some preprocessor macro(s) to enforce the derived type, e.g.:

g++ -DMY_CAR_TYPE=Fiat

and later

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

You could either declare all Car variants in a single header and #include that, or use something like the methods discussed in these threads - Generate include file name in a macro / Dynamic #include based on macro definition - to generate the #include from the same -D macro.

Community
  • 1
  • 1
underscore_d
  • 6,309
  • 3
  • 38
  • 64
  • This doesn't address the stated problem. – Lightness Races in Orbit Apr 18 '16 at 13:43
  • @LightnessRacesinOrbit Would you care to explain how not? "Given that, is it wrong to do what I propose and simply to select the correct .cpp in the makefile for the given project? What is the best way to do this?" Do you think swapping out cpp files is better than CRTP? – underscore_d Apr 18 '16 at 13:47
  • Your code provides types that can be used. It does not even discuss how you'd perform compile-time switching between them, which is what the question is about. – Lightness Races in Orbit Apr 18 '16 at 13:49
  • @LightnessRacesinOrbit Well, it hit half the question, namely 'Do you think this is a good idea' - no and here's an alternative. For the rest, see the edit in progress [ made before your comment elsewhere saying the same thing, honest O:-) ] – underscore_d Apr 18 '16 at 13:50
  • I dispute the notion that what OP suggested was in any way "hacky". The idea of "build-time polymorphism" (for lack of a better expression) is actually a fairly common technique in game engines (and presumably other projects) that need to run on multiple platforms. For example, a fairly generic "FileIO.h" might have several implementations based on the target platform (eg: Win32FileIO.cpp, PS4FileIO.cpp, etc.). If anything, the macro option seems hacky. – lhumongous Jul 11 '17 at 16:37
  • I dispute the notion that my post needs to be downvoted for a tiny part of it that expressed an opinion, rather than the vast majority that just explained an alternative, but sure. – underscore_d Jul 12 '17 at 10:22
  • I downvoted it because it's a sloppy answer. It's perfectly acceptable to have the build conditionally compile .cpp files based on certain criteria as @user2079303 states below, which should really be the accepted answer. There's a use-case for CRTP, and there's a use-case for build-time compilation. Each one is appropriate for different scenarios, and neither are necessarily "hacky". You spent way too many characters considering one option without really considering the other. – lhumongous Jul 20 '17 at 01:13
3

Choosing a .cpp file at compile time is OK and perfectly reasonable... if the ignored .cpp file would not compile. This is one way to choose a platform specific implementation.

But in general - when possible (such as in your trivial example case) - it's better to use templates to achieve static polymorphism. If you need to make a choice at compile time, use a preprocessor macro.

If the two implementations refer to separate projects which should never intersect but still are implementations for a given interface, I would recommend to extract that interface as a separate "project". That way the separate projects are not directly related to each other, even though they both depend on the third project which provides the interface.

eerorika
  • 232,697
  • 12
  • 197
  • 326
0

In your use case I think it would be best to use ifdef-blocks. This will be checked before compilation! This method is also sometimes used to distinct between different platforms for the same code.

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

In these code the compiler will ignore the ifdef-block of fiat, because only FERRARI is defined. This way you can still use methods you want to have for both cars. Everything you want different, you can put in ifdefs and simply swap out the defines.

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.

Otto V.
  • 169
  • 4
  • 11
  • 1
    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. – Lightness Races in Orbit Apr 18 '16 at 13:50
  • I just was about editing in something similar. Your suggestion is better tho. You can add it to my answer, if you like. – Otto V. Apr 18 '16 at 13:51
  • 1
    I believe that macros and ifdef is not a good idea in this case. As others mentioned: Use files selected during compile time or templates. ifdef is ungly! – Klaus Apr 18 '16 at 14:01
  • In terms of maintainability ifdefs are better than different files in this case. I'm sure this class will have many similar methods for both cars! With the compiler setup it's even better. – Otto V. Apr 18 '16 at 14:03
  • I would argue that CRTP is better, if you mean easier to understand and less prone to error, plus essential if multiple implementations are needed. Otherwise it's just a matter of taste, I guess. – underscore_d Apr 19 '16 at 09:56
  • Yes, it's just a little different and at least a matter of taste. If I'm not wrong the difference that **could** matter is that the *#ifdef*s are pre-compile. – Otto V. Apr 19 '16 at 10:01
  • Yeah, they run in the preprocessor, but what difference, if any, is that? As mentioned above, with CRTP you can equally specify which derived implementation to use via a compile-time macro - though, unlike here, you're not _required_ to; multiple types can coexist. Are you maybe concerned about possible overheads? `static_cast`s to the derived functions are trivially optimised into direct calls during compilation, thus introducing no overhead. Plus, if they are simple getters, the actual values can easily be inlined too, thus being equivalent to a properly optimised `const` or `constexpr` var. – underscore_d Apr 20 '16 at 12:59