std::function mit Funktionszeiger vergleichen?
-
@Schlangenmensch sagte in std::function mit Funktionszeiger vergleichen?:
Gibt es einen konkreten Use case? Vlt gibt es für dein eigentliches Problem eine schönere Lösung.
Das ist eigentlich eine nicht vorgesehene Nutzung. Die Funktionen (Callbacks übrigens) werden in einer Library in der Map Funktionsnummern zugeordnet, und normalerweise darüber gesucht und dann ausgeführt. Hier will ich in einem Testprogramm prüfen, ob das Matching die richtigen Funktionen zurückliefert - die Beziehung ist etwas komplexer, als nur 1:1 über Funktionsnummern, aber das führt hier wohl etwas weit. Normalerweise hätte ich die Map mit einem zusätzlichen Kennzeichen zur Funktion erweitert und das abgefragt, aber so etwas würde bei der eigentlichen Anwendung nur Overhead bedeuten.
-
Die Übertragung ins echte Leben funktioniert leider irgendwie doch nicht, ich bekomme an der nämlichen Stelle:
Test/main.cpp:1620:42: error: 'using MBSworker = class std::function<ModbusMessage(ModbusMessage)>' {aka 'class std::function<ModbusMessage(ModbusMessage)>'} has no member named 'target' auto wrk = RTUserver.getWorker(1, 3).target<ModbusMessage(*)(ModbusMessage)>(); ^~~~~~
Die Definition vom
MBSworker
enthält übrigensclass
nicht explizit:using MBSworker = std::function<ModbusMessage(ModbusMessage msg)>;
getWorker()
ist eine Memberfunktion des ObjektesRTUserver
, wenn das eine Rollen spielen sollte.Das ganze läuft auf einem ESP32 mit Arduino-Core - wenn ich Pech habe, ist
target
da nicht implementiert worden.
-
Selbstkommentar: man muss mit Compilerflag
-frtti
übersetzen!
-
Ja,
.target()
braucht RTTI, damit es prüfen kann ob der Typ eh übereinstimmt
-
@hustbaer sagte in std::function mit Funktionszeiger vergleichen?:
Ja,
.target()
braucht RTTI, damit es prüfen kann ob der Typ eh übereinstimmtWas natürlich für diesen speziellen Anwendungsfall ein klein wenig ineffizienter ist, als nur ein Pointer-Vergleich. Da die Adressen ja eindeutig sind, wäre die Typprüfung beim Vergleich mit der Adresse einer existierenden Funktion quasi implizit. Der Check macht allerdings Sinn, wenn man
target()
tatsächlich aufrufen will.Ich sehe aber nicht, wie das mit dem Interface von
std::function
anders ginge. Ich frage mich allerdings, weshalbstd::function::operator==
offenbar nur mitnullptr
und nicht einer anderen Funktion vergleichen kann. Dort hätte man das m.E. effizient implementieren können, auch ohne ungeprüfte Funktionspointer an den Anwender herauszugeben.
-
@Miq Ist jetzt schon ein paar Tage her, sorry.
Wenn es darum geht, den Suchprozess testbar zu machen, wäre es vlt schöner, wenn man den Suchprozess unabhängig von den tatsächlichen Funktionen testen kann.Man könnte z.B. eine Klasse implementieren, die das Suchen der Funktion übernimmt und eine Klasse, die die Funktionen bereit stellt. Die Klasse, die das Suchen implementiert, bekommt ein Objekt der Klasse, die die Funktionen implementiert, übergeben (dependency injection).
Dann kann man für den Test, die aufzurufenden Funktionen mocken, z.b. mit gMock (https://google.github.io/googletest/gmock_for_dummies.html), und dann im Testfall überprüfen, dass die richtige Funktione aufgerufen wurde.
-
@Finnegan sagte in std::function mit Funktionszeiger vergleichen?:
Was natürlich für diesen speziellen Anwendungsfall ein klein wenig ineffizienter ist, als nur ein Pointer-Vergleich. Da die Adressen ja eindeutig sind, wäre die Typprüfung beim Vergleich mit der Adresse einer existierenden Funktion quasi implizit. Der Check macht allerdings Sinn, wenn man
target()
tatsächlich aufrufen will.Das Target kann ja einen beliebigen Typ haben. Da können alle mögliche Dinge erfordern dass man den genauen Typ kennt.
Ich frage mich allerdings, weshalb
std::function::operator==
offenbar nur mitnullptr
und nicht einer anderen Funktion vergleichen kann. Dort hätte man das m.E. effizient implementieren können, auch ohne ungeprüfte Funktionspointer an den Anwender herauszugeben.Wie willst du das effizienter implementieren?
-
@hustbaer sagte in std::function mit Funktionszeiger vergleichen?:
Wie willst du das effizienter implementieren?
Ich dachte einfach intern vergleichen ob die beiden
std::function
-Objekte auf die selbe Speicheradresse verweisen. Ohne Typprüfung. Entweder der selbe Function Pointer oder das selbe Funktionsobjekt, Lambda, etc. Die Adressen sind ja eindeutig.Ist aber nur minimalst effizenter. Man spart sich halt das
typeid()
zur Laufzeit.
-
@Finnegan sagte in std::function mit Funktionszeiger vergleichen?:
@hustbaer sagte in std::function mit Funktionszeiger vergleichen?:
Wie willst du das effizienter implementieren?
Ich dachte einfach intern vergleichen ob die beiden
std::function
-Objekte auf die selbe Speicheradresse verweisen. Ohne Typprüfung. Entweder der selbe Function Pointer oder das selbe Funktionsobjekt, Lambda, etc. Die Adressen sind ja eindeutig.Verstehe ich nicht. Bei Funktionszeigern würde das funktionieren, ja. Aber nicht bei Funktionsobjekten. Es wird ja eine Kopie des Targets im
std::function
gespeichert, also eine Kopie des Funktionszeigers bzw. eben Funktionsobjects. Und komplexe Objekte kannst du nicht einfach mitmemcpy
vergleichen. D.h. du musst erst wieder den Typ kennen, um den passendenoperator ==
aufzurufen.
-
@hustbaer sagte in std::function mit Funktionszeiger vergleichen?:
@Finnegan sagte in std::function mit Funktionszeiger vergleichen?:
@hustbaer sagte in std::function mit Funktionszeiger vergleichen?:
Wie willst du das effizienter implementieren?
Ich dachte einfach intern vergleichen ob die beiden
std::function
-Objekte auf die selbe Speicheradresse verweisen. Ohne Typprüfung. Entweder der selbe Function Pointer oder das selbe Funktionsobjekt, Lambda, etc. Die Adressen sind ja eindeutig.Verstehe ich nicht. Bei Funktionszeigern würde das funktionieren, ja. Aber nicht bei Funktionsobjekten. Es wird ja eine Kopie des Targets im
std::function
gespeichert, also eine Kopie des Funktionszeigers bzw. eben Funktionsobjects. Und komplexe Objekte kannst du nicht einfach mitmemcpy
vergleichen. D.h. du musst erst wieder den Typ kennen, um den passendenoperator ==
aufzurufen.Man kann durchaus argumentieren, dass ein kopiertes Funktionobjekt eine andere Funktion ist. Die würden halt immer ungleich sein und das ganze tatsächlich nur mit Funktionszeigern sinnvoll funktionieren. "Logisch kaputt" wäre das nicht, nur eben für Funktionsobjekte nur bedingt hilfreich
-
@Finnegan Ja, könnte man. Dann wäre es aber besser, den
operator ==
gleich auf Funktionszeiger einzuschränken."Logisch kaputt" wäre das nicht, nur eben für Funktionsobjekte nur bedingt hilfreich
Doch, meiner Meinung nach schon. Was gleich ist und was nicht sollte immer noch der Typ, bzw. die dazugehörigen freien Operatoren entscheiden, und nicht
std::function
.
-
@hustbaer sagte in std::function mit Funktionszeiger vergleichen?:
@Finnegan Ja, könnte man. Dann wäre es aber besser, den
operator ==
gleich auf Funktionszeiger einzuschränken."Logisch kaputt" wäre das nicht, nur eben für Funktionsobjekte nur bedingt hilfreich
Doch, meiner Meinung nach schon. Was gleich ist und was nicht sollte immer noch der Typ, bzw. die dazugehörigen freien Operatoren entscheiden, und nicht
std::function
.Ja, das macht durchaus auch Sinn. Mir gings auch nur darum, das Fitzelchen an
typeid()
-Overhead einzusparen, das man speziell bei Funktionspointern wegen der eindeutigen Adresse nicht braucht. Möglicherweise ist das aber nicht wirklich die Mühe wert.Alternativ hätte
std::function::operator==
(der nur mitnullptr
vergleichen kann) auch Vergleiche zwischen zweistd::function
erlauben können, wenn zwischen deren Callable-Typen einoperator==
definiert ist. Für alles andere wäre der Operator dann nicht definiert. Im Zweifel könnte der User denoperator==
dann als freie Funktion einfach selbst definieren und z.B. solche Späße umsetzen wie Funktionspointer auch mit inkompatibler Signatur ausschliesslich über die Adresse zu vergleichen.
-
@Finnegan sagte in std::function mit Funktionszeiger vergleichen?:
Alternativ hätte std::function::operator== (der nur mit nullptr vergleichen kann) auch Vergleiche zwischen zwei std::function erlauben können, wenn zwischen deren Callable-Typen ein operator== definiert ist.
Wie soll das gehen?
Bei der Zuweisung weiss der Compiler ja nicht dass dann später evtl. mit einemstd::function
verglichen wird, und schon gar nicht was für ein Typ in diesem anderenstd::function
dann stecken wird.Maximal könnte man noch Vergleiche erlauben wenn beide
std::function
den exakt selben Typ enthalten. Nur sogar da hätte man einen blöden Fall den man erst zur Laufzeit erkennt. Nämlich wenn die zweistd::function
zwar den selben Typ enthalten, dieser aber gar nicht mit sich selbst vergleichbar ist. Das müsste man dann zur Laufzeit behandeln.false
zurückgeben? Exception werfen? Universum zerstören?Insgesamt würden sich Vergleiche mit
std::function
also deutlich anders verhalten, als Vergleiche der gespeicherten Typen. Und ich denke genau das ist der Grund warumstd::function
diese Vergleiche nicht implementiert.Als Ersatz dafür gibt es halt
target()
. Damit kann man dann bestimmte Vergleiche selbst implementieren. Das vermeidet Überraschungen.
-
@hustbaer Oh Mann! Danke für die hartnäckige Nachhilfe. Jetzt ist bei mir endlich der Groschen gefallen, dass
std::function
zwar die Funktionssignatur in ihrem Typ speichert, aber dennoch eine Type Erasure macht und zur Laufzeit an verschiedene Typen binden kann, welche die selbe Signatur unterstützen. Ich hätte mich ja auch fragen können, warum überhaupt mittypeid()
gearbeitet wird. Sorry, manchmal bin ich echt ein Holzkopf ... ich dachte echt die kennt ihre statischen Typen und könnte einfach zur Compile-Zeit schauen, ob einoperator==(T_LHS, T_RHS)
existiert.