2009-12-17 7 views
15

Ich muss ein std::map mit <std::string, fn_ptr> Paare implementieren. Die Funktionszeiger sind Zeiger auf Methoden derselben Klasse, der die Karte gehört. Die Idee besteht darin, direkten Zugriff auf die Methoden zu haben, anstatt einen Switch oder ein Äquivalent zu implementieren.Std :: Karte der Mitgliedsfunktionszeiger?

(Ich verwende std::string als Schlüssel für die Karte)

ich C++ ganz neu bin, so könnte einigen Pseudo-Code oder einen Link von jemandem, der mit Funktionszeiger über die Implementierung eine Karte spricht? (Zeiger auf Methoden derselben Klasse, der die Karte gehört)

Wenn Sie denken, dass es einen besseren Ansatz für mein Problem gibt, sind auch Vorschläge willkommen.

+2

Was ist das übergeordnete Ziel? In den meisten Fällen, in denen Leute Schalter benutzen, ist Polymorphie, was sie verwenden sollten. – outis

+1

+1 das Ersetzen von Schaltern mit Tabellensuchen ist etwas, was ich gerne in dynamischen Sprachen mache. –

+0

Ich habe eine Factory-Klasse, die mehrere Konstruktormethoden haben wird, die Klassen vom Typ A zurückgeben. Die Idee ist, etwas wie A * aClass = Factory-> newA ("key") zu machen; . Dann verwendet die Factory den "Schlüssel", um die korrekte Methode zum Erstellen einer A-Klasse aufzurufen, und gibt sie entsprechend zurück. – Goles

Antwort

24

Dies ist etwa die einfachste, die ich mir vorstellen kann. Beachten Sie, dass keine Fehlerprüfung durchgeführt wurde und die Karte wahrscheinlich sinnvollerweise statisch gemacht werden könnte.

#include <map> 
#include <iostream> 
#include <string> 
using namespace std; 

struct A { 
    typedef int (A::*MFP)(int); 
    std::map <string, MFP> fmap; 

    int f(int x) { return x + 1; } 
    int g(int x) { return x + 2; } 


    A() { 
     fmap.insert(std::make_pair("f", &A::f)); 
     fmap.insert(std::make_pair("g", &A::g)); 
    } 

    int Call(const string & s, int x) { 
     MFP fp = fmap[s]; 
     return (this->*fp)(x); 
    } 
}; 

int main() { 
    A a; 
    cout << a.Call("f", 0) << endl; 
    cout << a.Call("g", 0) << endl; 
} 
+0

Arbeitete gut, danke !, dieser Thread wurde sehr nützlich, da die Template-Implementierung von @outis auch wirklich gut ist. Will dies als die Antwort auf die spezifische Frage markieren, aber sicher sein, dass auch zu lesen. – Goles

+0

Oh ich verpasste die Auflösung des Bereichs: und es gab mir 'kann Argumente nicht konvertieren' Fehler. Das hat mir geholfen :) – SajithP

3

Eine Vorlage Implementierung könnte wie folgt aussehen:

class Factory { 
public: 
    enum which { 
     foo, bar, baz 
    }; 

    template<which w> 
    A* newA(...); 
    ... 
}; 
template<Factory::which w> 
A* Factory::newA(...) { 
    /* default implementation */ 
    throw invalid_argument(); 
} 
template<> 
A* Factory::newA<Factory::foo>(...) { 
    /* specialization for a 'foo' style A */ 
    ... 
} 
.... 

Dies erfordert, dass der Wert verwendet, um festzustellen, welche newA wird zum Zeitpunkt der Kompilierung bekannt sein genannt. Sie könnten möglicherweise einen const char * als Vorlagenparameter verwenden, aber es ist nicht garantiert, dass er bei allen Compilern funktioniert.

Eine weitere Option besteht darin, Hilfsfabriken zu erstellen, eine für jede Factory-Erstellungsmethode, und diese in der Map zu speichern. Dies ist kein großer Vorteil gegenüber dem Speichern von Methodenzeigern, aber Sie können eine Standarderstellungsmethode definieren und das Abrufen von Objekten aus der Map vereinfachen (Sie müssen nicht überprüfen, ob der Schlüssel vorhanden ist, da Sie eine Standardfactory erhalten). Auf der anderen Seite würde ein Eintrag für jeden unbekannten Schlüssel zur Karte hinzugefügt werden.

Wenn Sie eine enum anstelle einer Zeichenfolge für den Schlüsseltyp verwenden, müssen Sie sich keine Gedanken darüber machen, ob ein Schlüssel in der Karte vorhanden ist. Es ist zwar möglich, dass jemand einen ungültigen Schlüssel enum an newA übergibt, aber er muss das Argument explizit umsetzen, was bedeutet, dass es nicht versehentlich geschieht. Es fällt mir schwer, mir einen Fall vorzustellen, in dem jemand absichtlich einen Absturz verursachen würde in newA; Die potenziellen Szenarien beinhalten Sicherheit, aber ein Anwendungsprogrammierer könnte die App zum Absturz bringen, ohne Ihre Klasse zu verwenden.

+0

Fehle ich etwas oder funktioniert der obige Code nicht nur für Kompilierzeitwerte des aufgezählten Typs? Wenn ich eine Aufzählungsvariable vom Typ "which" habe, kann ich keine neuen A-Things abhängig vom Inhalt der Variablen erstellen. –

+0

@Neil: Du vermisst nichts. Das ist die große Einschränkung des Template-Ansatzes. Es mag nicht für Mr. Gandos Zwecke geeignet sein, aber es lohnt sich, darüber nachzudenken. – outis

+0

In diesem Fall sehe ich nicht den Vorteil Ihrer Vorgehensweise gegenüber benannten Funktionen. –

1

Eine weitere Option ist die Verwendung von Delegaten als Funktion von Funktionszeigern. This Delegate-Implementierung ist ziemlich schnell, unterstützt Polymorphismen und spielt gut mit STL-Containern. Man könnte so etwas wie haben:

class MyClass { 
public: 
    // defines 
    typedef fastdelegate::FastDelegate2<int, int, int> MyDelegate; 
    typedef std::map<std::string, MyDelegate> MyMap; 

    // populate your map of delegates 
    MyClass() { 
     _myMap["plus"] = fastdelegate::MakeDelegate(this, &Plus); 
     _myMap["minus"] = fastdelegate::MakeDelegate(this, &Minus); 
    } 

    bool Do(const std::string& operation, int a, int b, int& res){ 
     MyMap::const_iterator it = _myMap.find(operation); 
     if (it != _myMap.end()){ 
      res = it.second(a,b); 
      return true; 
     } 

     return false; 
    } 
private: 
    int Plus (int a, int b) { return a+b; } 
    int Minus(int a, int b) { return a-b; } 
    MyMap _myMap;  
};  
1

Da C++ 14, können wir eine generische Lambda zu bekommen verwenden leicht von Zeigern auf Member-Methoden loszuwerden.
Es folgt ein minimales, Ausführungsbeispiel einer Forward-Funktion mit einer generischen Lambda-Funktion aus:

#include<utility> 
#include<map> 
#include<string> 
#include<iostream> 

struct SomeClass { }; 
struct SomeOtherClass { }; 

struct Test { 
    void test(SomeClass) { std::cout << "SomeClass" << std::endl; } 
    void test(SomeOtherClass) { std::cout << "SomeOtherClass" << std::endl; } 
}; 

int main() { 
    Test test; 

    auto l = [&test](auto c){ test.test(c); }; 
    std::map<std::string, decltype(l)> m; 

    m.emplace("foo", l); 
    m.emplace("bar", l); 

    m.at("foo")(SomeClass{}); 
    m.at("bar")(SomeOtherClass{}); 
} 
+0

Das ist großartig und löst mein Problem (das eine einzigartige Topologie als die anderen hat). Können Sie bitte erklären, wie es funktioniert? Insbesondere gilt: auto l = [& test] (auto c) {test.test (c); }; std :: map m; ' – Klik

+0

Warten Sie eine Sekunde. Aufruf von 'm.at (" foo ") (SomeClass {}); m.at ("foo") (SomeOtherClass {}); 'gibt das gleiche Ergebnis. Was ist der Unterschied zwischen diesem und dem Aufruf von 'test.test (SomeClass {}); test.test (SomeOtherClass {}); '? – Klik

+0

[Sie geben tatsächlich unterschiedliche Ergebnisse] (https://wandbox.org/permlink/8MEU3gr1DKbxz4Ms). – skypjack

Verwandte Themen