2013-10-20 12 views
14

Mi chiedo se esiste un modo semplice per chiamare una funzione da una stringa. Conosco un modo semplice, usando 'se' e 'else'.Come chiamare una funzione con il suo nome (std :: string) in C++?

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

questo c'è l'approccio di base

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

è qualcosa di più semplice?

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

risposta

34

Quello che hai descritto è chiamato riflessione e C++ non lo supporta. Ad ogni modo potresti venire con qualche soluzione, ad esempio in questo caso molto concreto potresti usare uno std::map che mapperebbe nomi di funzioni (oggetti std::string) a puntatori di funzione, che in caso di funzioni con lo stesso prototipo potrebbe essere più facile di potrebbe sembrare:

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

noti che myMap[s](2,3) recupera il puntatore funzione mappata a stringa s e richiama questa funzione, passando 2 e 3 ad esso, rendendo l'uscita di questo esempio sia 5

+0

È possibile migliorare leggermente utilizzando std :: function e la sintassi degli elenchi di inizializzatori. –

+0

@LokiAstari: 'std :: function' è C++ 11 Immagino. – LihO

+0

@LihO: Mi piace il tuo approccio. Grazie. –

13

Utilizzando una mappa di stringa standard a funzioni standard.

#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

Bene, usando 'std :: functional' il codice è più pulito. –

+0

@AlanValejo: Certo che lo è :) Molte cose sono diventate molto più pulite in C++ 11 – LihO

0

Ciò è possibile a un livello inferiore se si intrecciano assieme con c, e da lì utilizzare un compilatore C++ con C e quindi utilizzare C++ e che potrebbe essere una forma di riflessione.

Il modo in cui funziona è tramite gli indirizzi delle funzioni. I sistemi operativi randomizzano gli indirizzi e quindi le variabili possono essere posizionate di conseguenza, ad esempio heapAddress + 1, heapAddress + 2, ecc. Funziona anche per le funzioni.

Il DNS è il Domain Name Server nel mondo di Internet. Interpreta un nome di dominio come http://www.example.com/ in un indirizzo IP, ad esempio forse 10.87.23.9/ (gli indirizzi IP che iniziano con 10 di solito sono reti private). Potresti usare l'idea di @LoHO e creare una mappa, ma degli indirizzi di funzione. Di nuovo, questo è un livello molto basso, nemmeno a livello di C++, e abbastanza coinvolto. Con una mappa degli indirizzi, data una stringa potresti decodificarla a un indirizzo tramite riferimenti a un array di long o int.

Dopo aver decodificato questo indirizzo, è necessario inserire le istruzioni binarie nei registri della CPU per l'esecuzione. Se decidi di utilizzare l'assembly o C per fare il lavoro dipende da cosa fai. È possibile utilizzare un approccio con stack di operandi e utilizzare C per eseguire ogni istruzione con i relativi parametri, oppure è possibile utilizzare assembly per posizionare le istruzioni, i dati o entrambi in registri della CPU per l'esecuzione.

Ecco come vorrei codificarlo (a condizione che questa è pseudo-codice):

linguaggio C/C++ file di intestazione:

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

linguaggio 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!"; 
} 

uscita:

Questo è ora in esecuzione tramite riflessione da una procedura di assemblaggio in una funzione C in una funzione C++ n!

+1

Che cos'è la mappa? Non includi alcuna intestazione standard o personalizzata che la contenga. Indipendentemente da ciò, per favore nessuno lo farà mai ... – Grault

+0

@Jesdisciple È pseudo-codice. Non è necessario definire Map perché l'utente definisce la classe Map. E modificherò per includere "map.h". Inoltre, perché scoraggiarlo? C'è qualcosa di sbagliato nel montaggio? Cosa c'è di sbagliato nell'ottenere valori dai registri della CPU? –

+0

Ah, pensavo ti riferissi a qualcosa di personalizzato o di terze parti. Scorporrei l'assemblea in generale perché è arcana, e questo in particolare perché è intelligente. – Grault

3

C'è un'altra possibilità che non è stata ancora menzionata, ovvero true reflection.

Un'opzione per questo è l'accesso alle funzioni esportate da un eseguibile o una libreria condivisa utilizzando le funzioni del sistema operativo per la risoluzione dei nomi agli indirizzi. Questo ha degli usi interessanti come il caricamento di due dll "concorrenti" in un programma "umpire", in modo che le persone possano risolverlo facendo combaciare i loro codici effettivi l'un l'altro (giocando Reversi o Quake, qualunque cosa).

Un'altra opzione è l'accesso alle informazioni di debug create dal compilatore. Sotto Windows questo può essere sorprendentemente facile per i compilatori che sono compatibili, dal momento che tutto il lavoro può essere scaricato su dll di sistema o DLL gratuite scaricabili da Microsoft. Parte della funzionalità è già contenuta nell'API di Windows.

tuttavia, che cade più nella categoria dei sistemi di programmazione - indipendentemente dalla lingua - e quindi si riferisce a C++ solo in quanto è la Sistemi di programmazione lingua per eccellenza.

+0

Sembra quasi una parte mancante della mia risposta sopra, che è esattamente ciò che fa anche la mia risposta, ma in un modo diverso: "usando le funzioni del sistema operativo per risolvere i nomi agli indirizzi" eccetto il mio è semplicemente diretto e richiede il tuo "algoritmo" 'per decodificare una stringa in un indirizzo. –

+0

Il punto dell'idea ** reflection ** è di accedere ai metadati creati dal compilatore anziché dover definire tabelle con nomi di funzioni e puntatori. Oltre ai nomi esportati e ai simboli di debug c'è anche RTTI (con alcuni compilatori), e anche approcci ibridi come l'elaborazione del file .map creato dal compilatore per mappare tra nomi di funzioni e indirizzi. Il progetto JEDI lo fa per gli stack walk ma può anche essere usato per trovare invece gli indirizzi per i nomi. L'idea è di utilizzare i dati generati dal compilatore che riflettono il codice sorgente invece di dover definire le proprie tabelle. – DarthGizka

Problemi correlati