2016-07-07 8 views
1

Ich benutze eine Third-Party-Bibliothek, über die ich keine Kontrolle habe. Es enthält zwei Klassen A und B, die beide ein Verfahren, mit dem gleichen Namen definieren:Gruppierung von zwei Typen zusammen

class A { 
    public: 
     ... 
     void my_method(); 
}; 

class B { 
    public: 
     ... 
     void my_method(); 
}; 

ich eine Klasse C erstellen möchten, die ein Element enthält, die der Klasse A oder B. Entscheidend ist, kann ich nur wissen, zur Laufzeit, ob ich A oder B brauche. Diese Klasse C ruft nur die Methode my_method auf.

Wenn ich den Code ändern könnte, würde ich einfach A und B ableiten von einer Elternklasse (Schnittstelle), die my_method definiert. Aber ich kann nicht.

Was ist die einfachste/eleganteste Art, diese Klasse C zu erstellen? Ich könnte natürlich C auf diese Weise definieren:

Aber ich möchte vermeiden, die Kosten der if-Anweisung jedes Mal zu bezahlen. Es fühlt sich auch unelegant an. Gibt es eine Möglichkeit, einen Super-Typ der Klasse A oder B zu erstellen? Oder irgendeine andere Lösung für dieses Problem?

+0

Vielleicht mit einer Vorlage Parameter als Basisklasse? –

+0

@ MattiaF. Die Frage lautet: "Ich kann nur zur Laufzeit ** wissen, ob ich A oder B brauche" – mvidelgauz

+0

Vorlage funktioniert nicht, da ich nur zur Laufzeit weiß, ob ich A oder B anrufen muss. – DevShark

Antwort

2

können Sie verwenden std::function (nicht sicher, dass es zwar eine bessere Leistung hat), so etwas wie:

class C { 
    public: 
     void call_my_method() { my_method(); } 

     void use_a(A* a) { my_method = [=]() { a->my_method() }; } 
     void use_b(B* b) { my_method = [=]() { b->my_method() }; } 
    private: 
     std::function<void()> my_method; 
}; 
+0

Ich bezweifle, dass ein Funktionsaufruf schneller sein wird als ein vorhersagbarer Zweig? Es ist jedoch nur ein Zweifel. Vielleicht könnte das schneller sein. Es würde Profiling benötigen. –

+0

Ich denke, es wäre schneller in der Pipeline-Architektur, die jetzt überall ist. – Arunmu

+0

Nun ja, nichts schlägt Profiling. – Arunmu

1

Nein; Irgendwann brauchst du Verzweigung. Das Beste, was Sie tun können, ist, die Verzweigung auf den Call-Stack hoch/runter zu hieven & dagger;, so dass mehr von Ihrem Programm innerhalb des figurativen if/else Konstrukts eingekapselt wird und der Zweig selbst weniger häufig ausgeführt werden muss. Natürlich müssen Sie dann mehr Quellcode Ihres Programms kopieren, was nicht ideal ist.

Die einzige Verbesserung, die ich zu diesem Zeitpunkt vorschlagen würde, ist ein Konstrukt wie boost::variant. Es tut im Grunde, was Sie bereits tun, aber nimmt weniger Speicher auf und hat nicht diese Ebene der Indirection (mit der so genannten getaggten Union statt). Es muss immer noch auf den Zugriff verzweigen, aber bis das Profiling gezeigt hat, dass dies ein großer Engpass ist (und Sie wahrscheinlich finden, dass Verzweigungsprognose lindert einen Großteil dieses Risikos) würde ich nicht weiter mit Ihren Änderungen gehen. & ddagger;

& dolch; Ich kann nie mehr, wie es geht lol

& ddagger; Eine solche Änderung könnte sein, einen Funktionszeiger (oder einen modernen std::function) bedingt zu initialisieren und dann die Funktion jedes Mal aufzurufen. Das ist jedoch viel Indirekt. Sie sollten ein Profil erstellen, aber ich würde erwarten, dass es in den Caches langsamer und härter wird. Ein OO-Purist könnte einen polymorphen Vererbungsbaum und einen virtuellen Versand empfehlen, aber das wird Ihnen keinen Nutzen bringen, wenn Sie sich erst einmal um die Leistung sorgen.

0

Wie über die Vererbung mit einer virtuellen Funktion unter Verwendung einer ‚Basisklasse‘ (C):

class C 
{ 
public: 
    virtual void do_method() = 0; 
}; 

class D : public C, private A 
{ 
    void do_method() { my_method(); } 
}; 

class E : public C, private B 
{ 
    void do_method() { my_method(); } 
} 

Dann wird diese Arbeit:

C * d = new D(); 
d->do_method(); 
+0

Jetzt ist es ein Funktionsaufruf _and_ eine virtuelle Tabelle nachschlagen. –

+0

@LightnessRacesinOrbit Es gibt keine freie Abstraktion. –

+0

@ n.m. Ich weiß das, aber das bedeutet nicht, dass Sie das teuerste auswählen müssen, das Sie finden können :) –

0

vorschlagen t o wickeln Sie Ihre A-und B-Objekte in eine Hilfsvorlage TProxy, die IProxy Schnittstelle realisiert. Klasse C (oder Consumer) arbeiten mit IProxy Schnittstelle und wird nicht wissen, über den Typ des Objekts innerhalb Proxy

#include <stdio.h> 

struct A { 
     void func() { printf("A::func\n"); } 
}; 

struct B { 
     void func() { printf("B::func\n"); } 
}; 

struct IProxy 
{ 
    virtual void doFunc() = 0; 
    virtual ~IProxy() {}; 
}; 

template<typename T> 
struct TProxy : public IProxy 
{ 
    TProxy(T& i_obj) : m_obj(i_obj) { } 

    virtual void doFunc() override { m_obj.func(); } 

private: 
    T& m_obj; 
}; 

class Consumer 
{ 
public: 
    Consumer(IProxy& i_proxy) : m_proxy(i_proxy) {} 

    void Func() { m_proxy.doFunc();} 

private: 
    IProxy& m_proxy; 
}; 

Main:

int main() 
{ 
    A a; 
    TProxy<A> aProxy(a); 

    B b; 
    TProxy<B> bProxy(b); 

    Consumer consumerA{aProxy}; 
    consumerA.Func(); 

    Consumer consumerB{bProxy}; 
    consumerB.Func(); 

    return 0; 
} 

Ausgang:

A::func 
B::func