2013-10-20 7 views
14

Ich frage mich, ob es eine einfache Möglichkeit gibt, eine Funktion aus einer Zeichenfolge aufzurufen. Ich kenne einen einfachen Weg mit 'wenn' und 'sonst'.Wie ruft man eine Funktion über ihren Namen (Std :: String) in C++ auf?

int function_1(int i, int j) { 
    return i*j; 
} 

int function_2(int i, int j) { 
    return i/j; 
} 

... 
... 
... 

int function_N(int i, int j) { 
    return i+j; 
} 

int main(int argc, char* argv[]) { 
    int i = 4, j = 2; 
    string function = "function_2"; 
    cout << callFunction(i, j, function) << endl; 
    return 0; 
} 

Dies ist der grundlegende Ansatz

int callFunction(int i, int j, string function) { 
    if(function == "function_1") { 
     return function_1(i, j); 
    } else if(function == "function_2") { 
     return function_2(i, j); 
    } else if(...) { 

    } ... 
    ... 
    ... 
    ... 
    return function_1(i, j); 
} 

Gibt es etwas einfacher?

/* New Approach */ 
int callFunction(int i, int j, string function) { 
    /* I need something simple */ 
    return function(i, j); 
} 

Antwort

34

Was Sie beschrieben haben, ist Reflexion genannt und C++ nicht unterstützt. Wie auch immer, Sie könnten in diesem sehr konkreten Fall eine std::map verwenden, die Namen von Funktionen (std::string Objekte) Funktionszeigern zuordnen würde, was im Fall von Funktionen mit dem gleichen Prototyp einfacher sein könnte es scheinen mag:

#include <iostream> 
#include <map> 

int add(int i, int j) { return i+j; } 
int sub(int i, int j) { return i-j; } 

typedef int (*FnPtr)(int, int); 

int main() { 
    // initialization: 
    std::map<std::string, FnPtr> myMap; 
    myMap["add"] = add; 
    myMap["sub"] = sub; 

    // usage: 
    std::string s("add"); 
    int res = myMap[s](2,3); 
    std::cout << res; 
} 

Beachten sie, dass myMap[s](2,3) den Funktionszeiger auf String abgebildet ruft s und ruft diese Funktion auf, vorbei an 2 und 3 es, so dass die Ausgabe dieses Beispiels 5

+0

Sie können das leicht verbessern, indem Sie std :: function verwenden und der Initialisierer listet die Syntax auf. –

+0

@LokiAstari: 'std :: function' ist C++ 11 ich denke. – LihO

+0

@LihO: Ich mag deinen Ansatz. Vielen Dank. –

13

Mit Hilfe einer Karte sein von Standard-String zu Standardfunktionen.

#include <functional> 
#include <map> 
#include <string> 
#include <iostream> 

int add(int x, int y) {return x+y;} 
int sub(int x, int y) {return x-y;} 

int main() 
{ 
    std::map<std::string, std::function<int(int,int)>> funcMap = 
     {{ "add", add}, 
      { "sub", sub} 
     }; 

    std::cout << funcMap["add"](2,3) << "\n"; 
    std::cout << funcMap["sub"](5,2) << "\n"; 
} 
+0

Gut, mit 'Std :: funktional' ist der Code sauberer. –

+0

@AlanValejo: Natürlich ist es :) Viele Dinge wurden weit sauberer in C++ 11 – LihO

0

Dies auf einem niedrigeren Niveau möglich ist, wenn Sie Montag mit c verflechten, und von dort aus einem C++ Compiler mit C zu verwenden und dann C++ verwenden, und dass eine Form der Reflexion sein könnte.

Die Funktionsweise funktioniert über die Funktionsadressen. Betriebssysteme randomisieren die Adressen, und dann können die Variablen entsprechend platziert werden, wie heapAddress + 1, heapAddress + 2, usw. Dies funktioniert auch für Funktionen.

Der DNS ist der Domain Name Server in der Welt des Internets. Es interpretiert einen Domain-Namen wie http://www.example.com/ zu einer IP-Adresse, sagen wir vielleicht 10.87.23.9/ (IPs beginnend mit 10 sind in der Regel private Netzwerke). Sie könnten die @ LiHO-Idee verwenden und stattdessen eine Karte, aber die Funktionsadressen erstellen. Auch dies ist ein sehr niedriges Niveau, nicht einmal auf C++ - Ebene und ziemlich involviert. Mit einer Karte der Adressen, gegeben einer Schnur, konnten Sie sie möglicherweise zu einer Adresse durch Verweise auf ein Array von long oder int decodieren.

Nachdem Sie diese Adresse dekodiert haben, müssen Sie die binären Anweisungen zur Ausführung in die CPU-Register setzen. Ob Sie sich für die Verwendung von Assembly oder C entscheiden, hängt davon ab, was Sie tun. Sie könnten einen Operanden-Stack-Ansatz verwenden und C verwenden, um jeden Befehl mit seinen Parametern auszuführen, oder Sie können assembly verwenden, um die Befehle, die Daten oder beide in die Register einer CPU zur Ausführung zu bringen.

Hier ist, wie ich würde es codieren (vorausgesetzt, dies ist Pseudocode):

Sprache C/C++ Header-Datei:

//reflection.h 
#pragma once 
class Reflection { 
public: 
    extern "C" { 
    static void exec(int& func_addr); 
    } 
} 

Sprache C++:

//mysourcefile.cpp 
#pragma once 
#include "reflection.h" 
#include "map.h" 

int main(){ 
    Map map; 
    Reflection.exec(map.decode("run()"); // executes a function called run() in the current class 
    wait(10);//seconds 
    return 0; 
} 

void run(){ 
cout<<"This is now running via reflection from an assembly procedure in a C function in a C++  function!"; 
} 

Ausgang:

Dies wird jetzt über Reflektion von einer Assembly-Prozedur in einer C-Funktion in einer C++ - Funktion ausgeführt n!

+1

Was ist Map? Sie enthalten keine standardmäßigen oder benutzerdefinierten Header, die diese enthalten. Egal, bitte tut das niemandem wirklich ... – Grault

+0

@Jesdisciple Es ist Pseudo-Code. Ich muss Map nicht definieren, da der Benutzer die Map-Klasse definiert. Und ich werde bearbeiten, um "map.h" einzuschließen. Warum sollte man das auch ablehnen? Ist mit der Montage etwas nicht in Ordnung? Was ist falsch daran, Werte aus CPU-Registern zu bekommen? –

+0

Ah, ich dachte, du beziehst dich auf etwas Brauchbares oder Drittanbieter. Ich würde die Versammlung im Allgemeinen ablehnen, weil es geheimnisvoll ist, und das vor allem, weil es schlau ist. – Grault

3

Es gibt eine andere Möglichkeit, die noch nicht erwähnt wurde, nämlich wahr Reflexion.

Eine Option hierfür ist der Zugriff auf Funktionen, die von einer ausführbaren Datei oder einer gemeinsam genutzten Bibliothek exportiert wurden, wobei Betriebssystemfunktionen zum Auflösen von Namen in Adressen verwendet werden. Dies hat interessante Anwendungen, wie das Laden von zwei "Kandidaten" -Dlls in ein "Umpire" -Programm, so dass die Leute es herausziehen können, indem sie ihre aktuellen Codes gegeneinander kämpfen lassen (Reversi oder Quake spielen, was auch immer).

Eine weitere Option ist der Zugriff auf die vom Compiler erstellten Debug-Informationen. Unter Windows kann dies für Compiler, die kompatibel sind, überraschend einfach sein, da die gesamte Arbeit auf System Dlls oder kostenlose Dlls heruntergeladen werden kann, die von Microsoft heruntergeladen werden können. Ein Teil der Funktionalität ist bereits in der Windows-API enthalten.

Dies fällt jedoch mehr in die Kategorie der Systemprogrammierung - unabhängig von der Sprache - und bezieht sich daher nur auf C++, da es die Systemprogrammiersprache par excellence ist.

+0

Dies scheint fast wie ein fehlender Teil meiner Antwort oben, was genau meine Antwort auch tut, aber auf eine andere Weise: "Betriebssystemfunktionen zum Auflösen von Namen in Adressen" außer meiner einfach ist direkt und erfordert Ihren eigenen Algorithmus 'um eine Zeichenfolge zu einer Adresse zu dekodieren. –

+0

Der Sinn der ** Reflection ** - Idee besteht darin, auf Metadaten zuzugreifen, die vom Compiler erstellt wurden, anstatt Tabellen mit Funktionsnamen und Zeigern definieren zu müssen. Neben exportierten Namen und Debug-Symbolen gibt es auch RTTI (mit einigen Compilern) und auch hybride Ansätze wie die Verarbeitung der vom Compiler erstellten .map-Datei zur Abbildung von Funktionsnamen und Adressen. Das JEDI-Projekt erledigt dies für Stackwalks, aber es kann auch verwendet werden, um Adressen für Namen zu finden. Die Idee ist, vom Compiler generierte Daten zu verwenden, die den Quellcode widerspiegeln, anstatt die eigenen Tabellen definieren zu müssen. – DarthGizka

Verwandte Themen