2008-09-11 20 views
3

Beim Refactoring einiger Legacy-C++ - Code habe ich möglicherweise einige Code-Duplizierung durch die Definition einer Variablen, die auf jede Klassenmethode mit der gleichen Signatur verweisen könnte. Nach einem kleinen Graben, fand ich, dass ich etwas wie die folgenden tun könnte:Zeiger auf C++ - Klassenmethoden

class MyClass 
{ 
protected: 
    bool CaseMethod1(int abc, const std::string& str) 
    { 
     cout << "case 1:" << str; 
     return true; 
    } 

    bool CaseMethod2(int abc, const std::string& str) 
    { 
     cout << "case 2:" << str; 
     return true; 
    } 

    bool CaseMethod3(int abc, const std::string& str) 
    { 
     cout << "case 3:" << str; 
     return true; 
    } 

public: 
    bool TestSwitch(int num) 
    { 
     bool (MyClass::*CaseMethod)(int, const std::string&); 

     switch (num) 
     { 
      case 1: CaseMethod = &MyClass::CaseMethod1; 
        break; 
      case 2: CaseMethod = &MyClass::CaseMethod2; 
        break; 
      case 3: CaseMethod = &MyClass::CaseMethod3; 
        break; 
     } 

     ... 

     bool res = CaseMethod(999, "hello world"); 

     ... 

     reurn res; 
    } 
}; 

Meine Frage ist - ist dies der richtige Weg, um dies zu? Sollte ich über alles nachdenken, was Boost zu bieten hat?

Edit ...

Ok, mein Fehler - ich sollte das Verfahren wie so sein Aufruf:

bool res = ((*this).*CaseMethod)(999, "Hello World"); 

Antwort

8

Was Sie dort haben, ist eine Zeiger-zu-Stab-Funktion. Es wird dein Problem lösen. Ich bin überrascht, dass Ihre "TestSwitch" -Funktion kompiliert, da die aufrufende Syntax etwas anders ist, als Sie erwarten könnten. Es sollte sein:

bool res = (this->*CaseMethod)(999, "hello world"); 

aber Sie könnten eine Kombination von boost :: Funktion finden und boost :: bind macht die Dinge ein wenig einfacher, als Sie die bizarre Aufrufsyntax zu vermeiden.

boost::function<bool(int,std::string)> f= 
    boost::bind(&MyClass::CaseMethod1,this,_1,_2); 

Natürlich, das wird es bindet an die aktuellen this Zeiger: Sie können die this Zeiger der Member-Funktion eines expliziter dritte Parameter machen können, wenn Sie mögen:

boost::function<bool(MyClass*,int,std::string)> f= 
    boost::bind(&MyClass::CaseMethod1,_1,_2,_3); 

Eine weitere Alternative sein könnte Verwenden Sie virtuelle Funktionen und abgeleitete Klassen, aber das erfordert möglicherweise größere Änderungen an Ihrem Code.

1

Sie können es auf jeden Fall tun, obwohl die CaseMethod Anruf nicht korrekt ist (es ist ein Zeiger auf Member-Funktion, so müssen Sie das Objekt angeben, auf dem die Methode aufgerufen werden soll). Der korrekte Aufruf wie folgt aussehen:

bool res = this->*CaseMethod(999, "hello world"); 

Auf der anderen Seite, würde ich boost::mem_fn empfehlen - Sie weniger Chancen, es zu vermasseln haben werden. ;)

0

Mit dem lokalisierten Beispiel, das Sie hier angegeben haben, ist nichts wirklich falsch, aber Zeiger auf Klassenmethoden können oft schwierig sein, "sicher" zu bleiben, wenn Sie sie in einem größeren Kontext verwenden, beispielsweise außerhalb der Klasse von oder in Verbindung mit einem komplexen Vererbungsbaum. Die Art und Weise, wie Compiler typischerweise Methodenzeiger verwalten, unterscheidet sich von "normalen" Zeigern (da es zusätzliche Informationen gibt, die über einen Code-Einstiegspunkt hinausgehen), und folglich gibt es eine Menge Einschränkungen in Bezug darauf, was Sie damit tun können.

Wenn Sie nur einfache Zeiger wie Sie beschreiben, dann werden Sie in Ordnung sein, aber vor komplexeren Anwendungen möchten Sie vielleicht einen Blick auf eine verallgemeinerte Funktor-System wie boost::bind. Diese können Zeiger auf fast jeden aufrufbaren Codezeiger nehmen und können auch instanzierte Funktionsargumente bei Bedarf binden.

1

Ich sehe den Unterschied zwischen Ihrem Aufruf und dem Aufruf der Methode innerhalb der switch-Anweisung nicht.

Nein, es gibt keine semantische oder Lesbarkeitsdifferenz.

Der einzige Unterschied, den ich sehe, ist, dass Sie einen Zeiger auf eine Methode nehmen und so dem Compiler verbieten, es zu inline oder optimiert jeden Aufruf dieser Methode.

3

Sie könnten auch einen Nachschlag bauen (wenn Ihr Schlüsselbereich sinnvoll ist), so dass Sie mit dem Schreiben am Ende:

this->*Methods[num](999, "hello world"); 

Dadurch wird der Schalter als auch, und macht die Bereinigung etwas mehr lohnt.

1

Ohne größeren Zusammenhang, es ist schwer, die richtige Antwort herauszufinden, aber ich nähe drei Möglichkeiten:

  • Aufenthalt mit normaler switch-Anweisung, keine Notwendigkeit, etwas zu tun. Dies ist die wahrscheinlichste Lösung

  • Verwenden Sie Zeiger auf Member-Funktion in Verbindung mit einem Array, wie @Simon sagt, oder möglicherweise mit einer Karte. Bei einer Fallanweisung mit einer großen Anzahl von Fällen kann dies schneller sein.

  • teilen Sie die Klasse in eine Reihe von Klassen auf, die jeweils eine Funktion zum Aufrufen enthalten und virtuelle Funktionen verwenden. Dies ist wahrscheinlich die beste Lösung, kaufen Sie es erfordern einige ernsthafte refatoring. Betrachten Sie GoF-Muster wie Staat oder Besucher oder Ähnliches.

0

Weitere Ansätze sind verfügbar, z. B. die Verwendung einer abstrakten Basisklasse oder spezielle Vorlagenfunktionen.

Ich werde die Basisklasse Idee beschreiben.

Sie können eine abstrakte Basisklasse definieren

class Base { virtual bool Method(int i, const string& s) = 0; }; 

Dann jedes Ihrer Fälle als eine Unterklasse, wie

class Case1 : public Base { virtual bool Method(..) { /* implement */; } }; 

An einem gewissen Punkt schreiben, werden Sie Ihre „num“ Variable erhalten, gibt an, welcher Test ausgeführt werden soll. Sie könnten eine Factory-Funktion schreiben, die diese Zahl annimmt (ich nenne sie which_case), und einen Zeiger auf Base zurückgeben und dann Methode von diesem Zeiger aufrufen.

Base* CreateBase(int which_num) { /* metacode: return new Case[which_num]; */ } 
// ... later, when you want to actually call your method ... 
Base* base = CreateBase(23); 
base->Method(999, "hello world!"); 
delete base; // Or use a scoped pointer. 

By the way, macht diese Anwendung mir C++ Wunsch statische virtuelle Funktionen unterstützt, oder so etwas wie „Art“ als builtin Typ - aber es funktioniert nicht.