2016-04-18 10 views
3

In dem Fall, in dem mehrere gewünschte Implementierungen für eine bestimmte Schnittstelle vorhanden sind, aber die gewünschte Implementierung vor der Kompilierungszeit bekannt ist, ist es falsch, die make-Datei einfach auf verschiedene Implementierungsdateien für denselben Header zu verweisen?Ist es in Ordnung, verschiedene Implementierungsdateien zu verwenden, um Polymorphie zu erreichen?

Zum Beispiel, wenn ein Programm definieren, ein Auto (Car.h)

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

und bei der Erstellung wir wissen, ob wir wollen, dass es ein Ferrari oder ein Fiat sein, die jeweils zu geben, entweder von der entsprechende Dateien:

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

während für den anderen Fall (wenig überraschend)

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

Nun, ich bin mir bewusst, dass ich sowohl Fiat als auch Ferrari abgeleitete Objekte von Car machen konnte und zur Laufzeit auswählen was ich gerne bauen würde. Ähnlich könnte ich es templatisieren und den Compiler dazu bringen, zur Kompilierzeit auszuwählen, was zu erstellen ist. In diesem Fall beziehen sich die beiden Implementierungen jedoch beide auf separate Projekte, die sich niemals überschneiden sollten.

Ist es falsch, zu tun, was ich vorschlage und einfach die richtige .cpp im Makefile für das gegebene Projekt auszuwählen? Was ist der beste Weg, dies zu tun?

+0

Das können Sie ja tun. Das ist ein bisschen eine schlechte Version von [Pimpl Idiom] (https://en.wikibooks.org/wiki/C%2B%2B_Programming/Idioms#Pointer_To_Implementation_.28pImpl.29). –

+3

Da dies ein statischer Polymorphismus ist, ist die Verwendung von CRTP ('class Fiat: public Car ') wahrscheinlich wesentlich idiomatischer als das Austauschen einer cpp-Datei und wird unbedingt benötigt, wenn mehrere Schnittstellen nebeneinander existieren sollen. –

+2

Sie könnten, aber Sie sollten nicht. Das ist ein ziemlich schlechter Weg Polymorphie zu erreichen und eine ziemlich schlechte Design-Wahl. – Nico

Antwort

1

Implementierung

Da dieser statischen Polymorphismus ist, ist die Merkwürdiger Recurring Template-Muster wahrscheinlich erheblich mehr idiomatische als eine CPP-Datei Swapping - was ziemlich hacky scheint. CRTP scheint erforderlich zu sein, wenn mehrere Implementierungen in einem Projekt koexistieren sollen, während sie mit einem erzwungenen Buildsystem mit einer einzigen Implementierung einfach zu verwenden sind. Ich würde sagen, seine gut dokumentierte Natur und die Fähigkeit, beides zu tun (da man nie weiß, was man später braucht), geben ihm den Vorteil.

Kurz gesagt, sieht CRTP etwas wie folgt aus:

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

(ich zuvor habe Präfix abgeleitet Implementierung Funktionen mit d_, aber ich bin nicht sicher, ob dies alles gewinnt, in der Tat, ist es wahrscheinlich Mehrdeutigkeit erhöht ...)

Um zu verstehen, was wirklich im CRTP passiert - es ist einfach, sobald Sie es bekommen! - Es gibt viele Führer herum. Sie werden wahrscheinlich viele Variationen finden und wählen Sie die, die Ihnen am besten gefällt.

Compile-Zeit Auswahl der Implementierung

Um wieder auf die andere Seite zu bekommen, wenn Sie eine der Implementierungen zur Compile-Zeit beschränken wollen, dann könnten Sie etwas Präprozessormakro verwenden (s) zu erzwingen die abgeleitete Typ, zB:

g++ -DMY_CAR_TYPE=Fiat 

und später

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

entweder könnten alle Auto Varianten in einem einzigen Header deklarieren und #include, oder verwenden Sie etwas wie die in diesen Threads beschriebenen Methoden - Generate include file name in a macro/DynamiC#include based on macro definition - zum Generieren des #include aus demselben -D Makro.

+0

Dies entspricht nicht dem angegebenen Problem. –

+0

@LightnessRacesinOrbit Möchten Sie erklären, wie nicht? "Ist es falsch, das zu tun, was ich vorschlage, und einfach die richtige .cpp im Makefile für das gegebene Projekt auszuwählen? Was ist der beste Weg, dies zu tun?" Denken Sie, dass das Austauschen von CPP-Dateien besser ist als CRTP? –

+0

Ihr Code enthält Typen, die verwendet werden können. Es wird nicht einmal diskutiert, wie Sie die Kompilierzeit zwischen ihnen ändern würden, worum geht es in der Frage. –

2

Wählen einer.CPP-Datei zur Kompilierzeit ist OK und vollkommen in Ordnung ... Wenn die ignorierte CPP-Datei nicht kompilieren würde. Dies ist eine Möglichkeit, eine plattformspezifische Implementierung auszuwählen.

Aber im Allgemeinen - wenn möglich (wie in Ihrem trivialen Beispielfall) - ist es besser, Vorlagen zu verwenden, um statische Polymorphie zu erreichen. Wenn Sie zum Zeitpunkt der Kompilierung eine Auswahl treffen müssen, verwenden Sie einen Präprozessor-Makro.

Wenn die beiden Implementierungen trennen Projekte beziehen, die nie schneiden sollen, aber noch Implementierungen für eine bestimmte Schnittstelle, würde ich diese Schnittstelle als separates „Projekt“ zu extrahieren empfehlen. Auf diese Weise sind die einzelnen Projekte nicht direkt miteinander verbunden, obwohl beide auf das dritte Projekt angewiesen sind, das die Schnittstelle bereitstellt.

0

In Ihrem Anwendungsfall denke ich, es wäre am besten zu verwenden ifdef-Blöcke. Dies wird vor der Kompilierung überprüft! Diese Methode wird manchmal auch verwendet, um verschiedene Plattformen für denselben Code zu unterscheiden.

// 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 diesem Code wird der Compiler den ifdef -Block von Fiat, ignorieren, da nur FERRARI definiert ist. Auf diese Weise können Sie immer noch Methoden verwenden, die Sie für beide Autos haben möchten. Alles was Sie wollen, können Sie in idefdefs einfügen und einfach die Definitionen austauschen.

Eigentlich statt die definiert von Auslagern, würden Sie Ihren Code in Ruhe lassen und die Definitionen auf der Linie GCC Befehl bieten die -D Build Schalter, je nachdem, welche Build-Konfiguration ausgewählt wurden.

+1

Anstatt die Definitionen zu vertauschen, belassen Sie den Code in Ruhe und stellen die Definitionen in der GCC-Befehlszeile mit dem Build-Schalter '-D' bereit, je nachdem, welche Build-Konfiguration ausgewählt wurde. –

+0

Ich wollte gerade etwas Ähnliches bearbeiten. Dein Vorschlag ist besser, tho. Sie können es meiner Antwort hinzufügen, wenn Sie möchten. –

+1

Ich glaube, dass Makros und Ifdef in diesem Fall keine gute Idee ist. Wie bereits erwähnt: Verwenden Sie Dateien, die während der Kompilierungszeit oder bei Vorlagen ausgewählt wurden. Ifdef ist ungemütlich! – Klaus