Wie am besten Java in C++ Programm umwandeln?
-
Nabend, bevor ich noch lange herumraten muss oder bei der KI nachfrage... Wie sähe das Pendant in C++ zu dieser Main-Methode in Java aus?
private enum Symbol { SLEEP1, TARGET1, ACTION1, ACTION2 } public static void main(String[] args) { if (args.length == 0) { exitApp(); } long sleep = 100; String target1 = "127.0.0.1"; Map<Integer, Map<Symbol, Integer>> fsm = new HashMap<>(); fsm.put( 0, Map.of( Symbol.SLEEP1, 1, Symbol.TARGET1, 2, Symbol.ACTION1, 3, Symbol.ACTION2, 3)); fsm.put( 1, Map.of( Symbol.TARGET1, 3, Symbol.ACTION1, 3, Symbol.ACTION2, 3)); fsm.put( 2, Map.of( Symbol.SLEEP1, 3, Symbol.ACTION1, 3, Symbol.ACTION2, 3)); fsm.put( 3, Map.of( Symbol.ACTION1, 3, Symbol.ACTION2, 3)); List<Runnable> runnables = new LinkedList<>(); int state = 0; for (String arg : args) { String[] parts = arg.split("=", 2); if (parts.length != 2) { exitApp(); } String key = parts[0]; String value = parts[1]; switch (key) { case "target1" -> { if (!fsm.get(state).containsKey(Symbol.TARGET1)) { exitApp(); } target1 = value; state = fsm.get(state).get(Symbol.TARGET1); } case "sleep" -> { if (!fsm.get(state).containsKey(Symbol.SLEEP1)) { exitApp(); } sleep = checkAndParse1(value); state = fsm.get(state).get(Symbol.SLEEP1); } case "a", "b" -> { switch (key) { case "a" -> { if (!fsm.get(state).containsKey(Symbol.ACTION1)) { exitApp(); } String finalTarget = target1; int finalNum = checkAndParse2(value); long finalSleep = sleep; runnables.add(() -> action1(finalTarget, finalNum)); runnables.add(() -> action3(finalSleep)); state = fsm.get(state).get(Symbol.ACTION1); } case "b" -> { if (!fsm.get(state).containsKey(Symbol.ACTION2)) { exitApp(); } String finalTarget = target1; int finalNum = checkAndParse2(value); long finalSleep = sleep; runnables.add(() -> action2(finalTarget, finalNum, 1000)); runnables.add(() -> action3(finalSleep)); state = fsm.get(state).get(Symbol.ACTION2); } default -> exitApp(); } } default -> exitApp(); } } if (runnables.isEmpty()) { exitApp(); } runnables.remove(runnables.size() - 1); // Remove last action // Run all actions runnables.forEach(Runnable::run); }Vermutlich ginge es (viel) einfacher, da C++ ja Funktionspointer kennt?
-
@Lennox Wie wäre es, wenn du es erstmal selbst versuchst?
-
@Schlangenmensch
Ja gut, ich versuche es erst selbst... aber erwartet keine Wunder von mir...Die FSM (also das argument parsing...) ist im Prinzip so, dass eine beliebige Reihenfolge von target1 und/oder sleep1 erlaubt ist, aber die a/b-Auflistung immer danach folgen muss...
Beispiel:
program.exe target=1.1.1.1 sleep=2000 a=1 b=3 a=1
Oder auch:
program.exe a=1
program.exe a=1 b=3 a=1
program.exe sleep=2000 a=1 b=3 a=1
program.exe target=1.1.1.1 a=1 b=3 a=1
program.exe sleep=2000 target=1.1.1.1 a=1 b=3 a=1Jetzt sollte es deutlich sein. Ungültig wäre z. B.: program.exe target=1.1.1.1
Muster:
program.exe (sleep=<int_ms>) (target=<ip/host>) [a/b]=<int> ...Ich war mir erst nicht sicher, ob man dafür wirklich eine FSM braucht, aber offenbar genügt ein einfacher https://de.wikipedia.org/wiki/Behavior_Tree für die Verarbeitung der Argumente und das Starten der Runnables nicht, oder?
-
@Lennox Macht das Programm denn in Java was du erwartest? Ich sehe auf Anhieb nämlich nicht, warum dein ungültiges Beispiel von deiner
mainverworfen werden sollte. Tipp: In kleinere Teile zerlegen und Unit Tests schreibenMir ist nicht klar, warum deine State Machine brauchst? Es gibt verschiedene Dinge, die man damit lösen kann, aber um Kommandozeilenparameter zu parsen und überprüfen würde ich die jetzt nicht nehmen. Mir wäre da auch nicht klar, was die Zustände und die Zustandsübergänge sind.
Die
statevariable wird auch nur gesetzt und nirgends verwendet. D.h. du könntest den ganzen fsm Kram löschen und die Funktion würde exakt dasselbe machen.Wenn ich dich richtig verstehe, kann man ein Target angeben und man kann einen Sleep angeben. Muss man aber nicht, wenn nicht gibt es einen default.
Dann gibt es verschiedene Aktionen, die einen Parameter benötigen, (eine beliebige Nummer).
Was irgendwie eine triviale Möglichkeit wäre, wäre sowas:
#include <vector> #include <algorithm> #include <iostream> #include <string> enum class Action { A, B }; void action1(std::string target, int action) { } void action2(std::string target, int action, int magic) { } void action3(int sleep) { } int main(int argc, char* argv[]) { int i = 1; //first argument is program name std::vector<std::pair<Action, int>> actions; //default values std::string target="127.0.01"; int sleep = 1000; //parse command line while (i < argc) { std::string arg{argv[i]}; size_t equals = arg.find('='); if (equals != std::string::npos) { std::string key = arg.substr(0, equals); std::string value = arg.substr(equals + 1); if(key == "target1") { target=value; } if(key == "sleep") { sleep = std::stoi(value); } if(key == "a") { actions.emplace_back(std::pair(Action::A, std::stoi(value) )); } if(key == "b") { actions.emplace_back(std::pair(Action::B, std::stoi(value) )); } } } //Perfom actions for(auto& action : actions) { switch (action.first) { case Action::A: { action1(target, action.second); action3(sleep); break; } case Action::B: { action2(target, action.second, 1000); action3(sleep); break; } default: break; } } return 0; }
-
@Schlangenmensch sagte in Wie am besten Java in C++ Programm umwandeln?:
Macht das Programm denn in Java was du erwartest?
Ja.
Ich habe noch vergessen, eine doppelte Angabe von sleep oder target soll NICHT möglich sein. Das geht nicht ohne FSM.
@Schlangenmensch sagte in Wie am besten Java in C++ Programm umwandeln?:
Mir wäre da auch nicht klar, was die Zustände und die Zustandsübergänge sind.
Zustand: Bei welcher Eingabe man gerade ist. Übergang: vom aktuellen Zustand durch die nächste Eingabe zum folgenden Zustand.
0 = akzeptiere alles
1 = sleep wurde angegeben, es darf nur noch target oder die Liste angegeben werden
2 = target wurde angegeben, es darf nur noch sleep oder die Liste angegeben werden
3 = nur noch die Liste darf angegeben werden, gleichzeitig Endzustand@Schlangenmensch sagte in Wie am besten Java in C++ Programm umwandeln?:
Die statevariable wird auch nur gesetzt und nirgends verwendet. D.h. du könntest den ganzen fsm Kram löschen und die Funktion würde exakt dasselbe machen.
Das stimmt nicht, schaue noch mal genau hin. Wenn
fsm.get(state).containsKey(Symbol...)NICHT zutrifft, wird die Anwendung sofort beendet.
-
@Lennox sagte in Wie am besten Java in C++ Programm umwandeln?:
Ich habe noch vergessen, eine doppelte Angabe von sleep oder target soll NICHT möglich sein. Das geht nicht ohne FSM.
Oder mit jeweils einem bool Schalter.
@Lennox sagte in Wie am besten Java in C++ Programm umwandeln?:
fsm.get(state).containsKey(Symbol...)
Ah... deine erlaubten Zustandsübergänge... Kommentare wäre praktisch.
Mir scheint dein Ansatz overengineered zu sein, aber jeder so wie er mag.
-
@Schlangenmensch sagte in Wie am besten Java in C++ Programm umwandeln?:
overengineered
Na ja, Ansichtssache... Hierbei mit booleschen Variablen zu hantieren, das würde irgendwann unübersichtlich werden.
Eigentlich wollte ich nur wissen, wie
Map<Integer, Map<Symbol, Integer>> fsm = new HashMap<>();in C++ geht, sowie wie
List<Runnable> runnables = new LinkedList<>();undrunnables.forEach(Runnable::run);dann geht...Anstatt
<Runnable>dann Funktionen? Und wie ist das mit den konkreten Parametern der Funktionen?In Java kann ich einfach
runnables.add(() -> action1(finalTarget, finalNum));schreiben, wenn die Parameter effektiv final sind. Vermutlich geht das in C++ aber nicht...
-
@Lennox
std::map<int, std::map<Symbol, int>> fmt;für deine
runnablesmusst du ein bisschen schauen, da du nur Funktionspointer mit der selben Signatur in einen Container bekommst. Aberstd::functionundstd::bindsind deine Freunde.
-
@Schlangenmensch So?
#include <map> #include <vector> #include <algorithm> #include <iostream> #include <string> enum class Symbol { NA, SLEEP, TARGET, ACTION1, ACTION2 }; enum class Action { A, B, C }; typedef struct action_args { std::string target; int val; int magic; int sleep; } ACTION_ARGS; void action1(std::string target, int val) { std::cout << "action1: " << target << ", " << val << std::endl; } void action2(std::string target, int val, int magic) { std::cout << "action2: " << target << ", " << val << ", " << magic << std::endl; } void action3(int sleep) { std::cout << "action3: " << sleep << std::endl; } void exitApp(std::string cause) { std::cout << "WRONG: " << cause << std::endl; std::exit(0); } int main(int argc, char *argv[]) { // default values std::string target = "0.0.0.0"; int sleep = 1000; std::map<int, std::map<Symbol, int>> fsm; fsm[0][Symbol::SLEEP] = 1; fsm[0][Symbol::TARGET] = 2; fsm[0][Symbol::ACTION1] = 3; fsm[0][Symbol::ACTION2] = 3; fsm[1][Symbol::TARGET] = 3; fsm[1][Symbol::ACTION1] = 3; fsm[1][Symbol::ACTION2] = 3; fsm[2][Symbol::SLEEP] = 3; fsm[2][Symbol::ACTION1] = 3; fsm[2][Symbol::ACTION2] = 3; fsm[3][Symbol::ACTION1] = 3; fsm[3][Symbol::ACTION2] = 3; std::vector<std::pair<Action, ACTION_ARGS>> actions; // parse command line int i = 1; int state = 0; while (i < argc) { std::string arg{argv[i++]}; size_t equals = arg.find('='); if (equals != std::string::npos) { std::string key = arg.substr(0, equals); std::string value = arg.substr(equals + 1); Symbol symbol = Symbol::NA; if (key == "sleep") { symbol = Symbol::SLEEP; } if (key == "target") { symbol = Symbol::TARGET; } if (key == "a") { symbol = Symbol::ACTION1; } if (key == "b") { symbol = Symbol::ACTION2; } if (symbol == Symbol::NA) { exitApp("Unkown arg"); } if (!fsm[state].count(symbol)) { exitApp("Not allowed arg"); } if (symbol == Symbol::SLEEP) { sleep = std::stoi(value); } if (symbol == Symbol::TARGET) { target = value; } if (symbol == Symbol::ACTION1) { actions.emplace_back(std::pair(Action::A, ACTION_ARGS{target, std::stoi(value), -1, sleep})); actions.emplace_back(std::pair(Action::C, ACTION_ARGS{"", -1, -1, sleep})); } if (symbol == Symbol::ACTION2) { actions.emplace_back(std::pair(Action::B, ACTION_ARGS{target, std::stoi(value), 1000, sleep})); actions.emplace_back(std::pair(Action::C, ACTION_ARGS{"", -1, -1, sleep})); } state = fsm[state][symbol]; } } if (actions.empty()) { exitApp("No args ..."); } actions.pop_back(); // Pop last unneeded action // Perfom actions for (const auto &action : actions) { const auto &a = action.second; switch (action.first) { case Action::A: { action1(a.target, a.val); break; } case Action::B: { action2(a.target, a.val, a.magic); break; } case Action::C: { action3(a.sleep); break; } default: exitApp("Should not happen"); } } return 0; }Ungetestet. Und es ist länger geworden... Das muss doch irgendwie eleganter und robuster gehen?
$ ./a.out target=localhost sleep=2500 a=123 a=23 b=34 a=1 action1: localhost, 123 action3: 2500 action1: localhost, 23 action3: 2500 action2: localhost, 34, 1000 action3: 2500 action1: localhost, 1Was ist, wenn
stoieinen schlechten Tag hat? Und... dasACTION_ARGSist doch unschön.
-
Es geht auch ohne das störende
typedef struct action_args
...#include <map> #include <vector> #include <algorithm> #include <iostream> #include <string> #include <functional> enum class Symbol { NA, SLEEP, TARGET, ACTION1, ACTION2 }; enum class Action { A, B, C }; void action1(std::string target, int val) { std::cout << "action1: " << target << ", " << val << std::endl; } void action2(std::string target, int val, int magic) { std::cout << "action2: " << target << ", " << val << ", " << magic << std::endl; } void action3(int sleep) { std::cout << "action3: " << sleep << std::endl; } void exitApp(std::string cause) { std::cout << "WRONG: " << cause << std::endl; std::exit(0); } int main(int argc, char *argv[]) { // default values std::string target = "0.0.0.0"; int sleep = 1000; std::map<int, std::map<Symbol, int>> fsm; fsm[0][Symbol::SLEEP] = 1; fsm[0][Symbol::TARGET] = 2; fsm[0][Symbol::ACTION1] = 3; fsm[0][Symbol::ACTION2] = 3; fsm[1][Symbol::TARGET] = 3; fsm[1][Symbol::ACTION1] = 3; fsm[1][Symbol::ACTION2] = 3; fsm[2][Symbol::SLEEP] = 3; fsm[2][Symbol::ACTION1] = 3; fsm[2][Symbol::ACTION2] = 3; fsm[3][Symbol::ACTION1] = 3; fsm[3][Symbol::ACTION2] = 3; std::vector<std::function<void()>> actions; // parse command line int i = 1; int state = 0; while (i < argc) { std::string arg{argv[i++]}; size_t equals = arg.find('='); if (equals != std::string::npos) { std::string key = arg.substr(0, equals); std::string value = arg.substr(equals + 1); Symbol symbol = Symbol::NA; if (key == "sleep") { symbol = Symbol::SLEEP; } if (key == "target") { symbol = Symbol::TARGET; } if (key == "a") { symbol = Symbol::ACTION1; } if (key == "b") { symbol = Symbol::ACTION2; } if (symbol == Symbol::NA) { exitApp("Unkown arg"); } if (!fsm[state].count(symbol)) { exitApp("Not allowed arg"); } if (symbol == Symbol::SLEEP) { sleep = std::stoi(value); } if (symbol == Symbol::TARGET) { target = value; } if (symbol == Symbol::ACTION1) { actions.push_back([=]() { action1(target, std::stoi(value)); }); actions.push_back([=]() { action3(sleep); }); } if (symbol == Symbol::ACTION2) { actions.push_back([=]() { action2(target, std::stoi(value), 1000); }); actions.push_back([=]() { action3(sleep); }); } state = fsm[state][symbol]; } } if (actions.empty()) { exitApp("No args ..."); } actions.pop_back(); // Pop last unneeded action // Perfom actions for (const auto &action : actions) { action(); } return 0; }Langsam mag ich C++.

-
enum class Actionbräuchte man jetzt auch nicht mehr (übersehen)...Könnte mir jemand
actions.push_back([=]() { action1(target, std::stoi(value)); });im Einzelnen erklären? Wie funktioniert das? Ginge es auch mit weniger Zucker? Ist das das 1:1-Äquivalent zum Java-Code oben?
-
Da passieren mehrere Dinge...
[=]() { action1(target, std::stoi(value)); }erzeugt ein anonymes Lambda ohne Funktionsparameter, das die Parameter
targetundvalueals Kopie bindet. Beim Aufruf des Lambda wird die Funktionaction1mit den beiden Parametern aufgerufen.
Das Lambda selbst wird anschließend in einstd::function<void()>gekapselt und in den Vektor eingefügt.
-
@DocShoe Danke, funktioniert diese Funktionsparameterbindung intern/automatisch? Gibt es noch Alternativen, um die Funktion, Paramter und den Aufruf zu kapseln?
Btw., @Schlangenmensch anstatt
size_tsolltestd::size_tverwendet werden.
-
@Lennox Und warum? Normalerweise hätte ich
const autogeschrieben, wollte aber im Vergleich zu Java die Typen explizit hinschreiben.Mit dem Capture
[=]gibst du an, dass das Lambda alle lokalen Variablen, die das Lambda nutzt als Kopie übernehmen solle. Kopie ist der default. Du kannst auch angeben, was genau Verfügbar sein soll:[target, value]() { action1(target, std::stoi(value)); }Werte die als Kopie gekapselt werden sind auch direkt const in dem Lambda. Wenn du die im Lambda verändern willst, musst du das explizit
mutablemachen[target, value]() mutable { action1(target, std::stoi(value)); }Du kann mit
&Werte als Referenz übergeben (targetwird als Referenz gebunden)[&target, value]() { action1(target, std::stoi(value)); }und ein Lambda kann Parameter haben, die beim Aufruf übergeben werden:
[value](const string& target) { action1(target, std::stoi(value)); }Wie ich weiter oben geschrieben habe, kannst du Funktionen mit den Parametern auch mit
std::bindin std::function kapseln:actions.push_back(std::bind(action2, target, std::stoi(value), 1000));Das hier:
runnables.add(() -> action2(finalTarget, finalNum, 1000));ist doch in Java auch ein Lambda, was du da hinzufügst, oder? Daher sollte das mit dem Lambda schon recht "äquivalent" sein, wobei ich mich bei Java nicht mit den Feinheiten auskenne.
-
@Schlangenmensch sagte in Wie am besten Java in C++ Programm umwandeln?:
() -> action2(finalTarget, finalNum, 1000)
Das ist eine statische, anonyme Klasse, welche das Runnable Interface realisiert.
https://docs.oracle.com/javase/8/docs/api/java/lang/Runnable.html
Das Interface hat nur eine Methode, die implementiert werden muss, daher ist die Lambda-Schreibwese möglich.
Innerhalb der Methode innerhalb der Lambda-Klasse wird einfach nur action2 aufgerufen, deshalb können wir uns das return... usw. sparen.
Eigentlich sind das hier sogar zwei verschränkte Lambdas, die hier zum Einsatz kommen, eins für die implementierende Klasse und eins für die implementierte Methode.
Ich vermute, die Parameterbindung verhält sich hier zu 1:1 wie
actions.push_back(std::bind(action2, target, std::stoi(value), 1000));- sprich, die Ausdrücke sind quasi Äquivalent (nur, dass es in Java natürlich keine Referenzen gibt, da call-by-value... deshalb wird auch verlangt, dass die eingehenden Parameter, also Variablen, final sind).Hilft dir das erst mal?