Check einer Konstanten auf Eindeutigkeit zur Compilezeit



  • @Th69 yessir, das hat @DocShoe ja auch geschrieben und so hat es dann auch funktioniertn



  • Nicht ganz, unsere Aussagen sind leicht unterschiedlich...


  • Mod

    @hustbaer sagte in Check einer Konstanten auf Eindeutigkeit zur Compilezeit:

    @Columbo sagte in [Check einer Konstanten auf Eindeutigkeit zur Compilezeit](/forum

    "bringt nicht viel" sehe ich anders, weil es immer noch einen signifikanten Teil der failure modes abdeckt; ausserdem sehe ich nicht, welche Methode in der Lage waere, diesen failure mode abzudecken?

    Fällt mir auch nix ein wie man das abdecken könnte.

    Mir schon, Du könntest ein non-inline external symbol in der TU definieren lassen, dessen Typ mit dem Code parametrisiert ist, was dann zu Linkerfehlern führt wenn mehrere TUs den gleichen Code verwenden (technisch ill-formed NDR aber praktisch immer diagnostiziert). Das funktioniert in meiner Lösung aber nicht, weil eine class-scope friend Definition die Funktion implizit inline macht. Ob das aber ohne ekligen boilerplate machbar ist...

    Edit: das ergibt eigentlich keinen Sinn, weil die meisten User ihre Codes doch in einem Header definieren wuerden. Du muesstest also eigentlich noch einen drauf setzen: wenn das selbe Code-Symbol in mehreren TUs definiert ist, ist es ok, aber wenn verschiedene Code-Symbole mit dem gleichen Typ ueber mehrere TUs definiert werden, dann sollen diese wiederum dasselbe non-inline Dummy-Symbol definieren, was dann zum Fehler fuehrt. Klingt aber knifflig umzusetzen.

    Um den Anwendern der Library die Möglichkeit zu geben, auch diese Nichtstandardfunktionen abzubilden, sollen sie in der Lage sein, diese im Userspace zu codieren. Ich möchte aber vermeiden, meinen Librarycode durch überall eingestreute Abfragen nach Sonderlocken aufzublähen, und diese zusätzlichen Funktionscodes einfach genauso behandeln wie die eingebauten, ohne dass die Library angefasst werden muss

    ... und warum genau ist es so entscheidend, dass der User keinesfalls einen Code zweimal benennen darf? Letztlich kann der User doch viele Dinge falsch machen. Man arbeitet unter der Annahme, dass er sorgfältig ist, und Codes zu definieren erfordert immer eine gewisse Akribie, weil man alles mit einer oder mehreren APIs abgleichen muss.



  • @Columbo sagte in Check einer Konstanten auf Eindeutigkeit zur Compilezeit:

    ... und warum genau ist es so entscheidend, dass der User keinesfalls einen Code zweimal benennen darf? Letztlich kann der User doch viele Dinge falsch machen. Man arbeitet unter der Annahme, dass er sorgfältig ist, und Codes zu definieren erfordert immer eine gewisse Akribie, weil man alles mit einer oder mehreren APIs abgleichen muss.

    Das kam dann falsch rüber - es ist völlig egal, wie die Codes benannt werden, es darf eben jeden nur genau einmal geben. Und die von der Library vorgegebenen sind schon da und können so nicht mit einer anderen Bedeutung versehen werden.
    Der Funktionscode hat nicht nur eine Nummer, sondern auch einen Typ und einen eigenen Satz von Parametern in Modbus-Nachrichten, die die Funktionen bedienen. Das ist in der Rumpfklasse meines Beispiels noch nicht enthalten, weil ich das Beispiel nicht unnötig aufblähen wollte. Deswegen muss sich FC13 immer wie FC13 darstellen, auch wenn er gerade Otto heißt.



  • @Miq sagte in Check einer Konstanten auf Eindeutigkeit zur Compilezeit:

    Der Funktionscode hat nicht nur eine Nummer, sondern auch einen Typ und einen eigenen Satz von Parametern in Modbus-Nachrichten, die die Funktionen bedienen. Das ist in der Rumpfklasse meines Beispiels noch nicht enthalten, weil ich das Beispiel nicht unnötig aufblähen wollte. Deswegen muss sich FC13 immer wie FC13 darstellen, auch wenn er gerade Otto heißt.

    Ich denke es wäre jetzt an der Zeit dass du mal ein Beispiel zeigst worum es wirklich geht.

    Klingt für mich nämlich danach als ob das alles leicht mit einem Klassentemplate lösbar sein sollte (keine Default-Implementierung, nur vollständige Spezialisierungen für die jeweiligen Function-Codes).


  • Mod

    @Miq sagte in Check einer Konstanten auf Eindeutigkeit zur Compilezeit:

    @Columbo sagte in Check einer Konstanten auf Eindeutigkeit zur Compilezeit:

    ... und warum genau ist es so entscheidend, dass der User keinesfalls einen Code zweimal benennen darf? Letztlich kann der User doch viele Dinge falsch machen. Man arbeitet unter der Annahme, dass er sorgfältig ist, und Codes zu definieren erfordert immer eine gewisse Akribie, weil man alles mit einer oder mehreren APIs abgleichen muss.

    Das kam dann falsch rüber - es ist völlig egal, wie die Codes benannt werden, es darf eben jeden nur genau einmal geben. Und die von der Library vorgegebenen sind schon da und können so nicht mit einer anderen Bedeutung versehen werden.

    Das ist doch einerlei. Das "Benennen" eines Codes sollte heissen, eine Variable zu definieren, die diesen Code repräsentiert. Ich meinte damit, er darf keine zwei Code-Proxies definieren, die den gleichen Code darstellen.



  • Für mich klingt das alles nach einem XY Problem...



  • @DocShoe sagte in Check einer Konstanten auf Eindeutigkeit zur Compilezeit:

    XY Problem

    Öh? Wenn ich nicht alle Details erwähnt habe, dann, um die Komplexität zu verringern, nicht, weil ich irgendwas verbergen wollte.



  • Was spricht denn gegen eine Klasse, die sämtliche Funktionscodes per virtueller Funktion abdeckt? Wenn der Benutzer besondere Behandlung will kann er die entsprechende Funktion überschreiben und mit eigener Funktionalität ausstatten.
    Oder du baust eine Tabelle mit Funktionszeigern, die für jeden Funktionscode eine Funktion mit dem Standardverhalten enthält. Wenn ein Benutzer andere Funktionalität braucht kann er seine eigene Funktion für die entsprechende Funktionscodes registrieren.
    Ich habe den Eindruck, du möchtest zu viel auf globaler Ebene einschränken, das ginge lokal mit iwas Gekapselten deutlich einfacher.
    Ist aber nur ´ne Vermutung, was du genau hast und wo du hin willst weiß ich ja nicht.



  • TL;DR: Ich brauche benutzerdefinierte Funktionscodes, die nicht mit denen der Library bekannten kollidieren und sich mit den Librarymethoden problemlos bearbeiten lassen.

    Okay, dann beschreibe ich mal Modbus und wie ich das behandele - vielleicht hilft das ja weiter...
    Ich lasse dennoch die diversen Protokolldetails weg, die sich auf die Transportschicht (TCP, RTU) beziehen und bleibe bei den eigentlichen Messages des Modbus-Standards.

    Auf einem Modbus gibt es genau einen Client, der Requests stellt, und mehrere Server, die darauf mit Responses antworten. Nur ein Request startet eine Kommunikation, die Server dürfen nicht spontan senden.

    Jede Modbus-Message beginnt mit einem Byte mit der Adresse des Servers, der angesprochen werden soll, und einem Funktionscode. Dieser Funktionscode benutzt die Bits 0-6 des Bytes, das Bit 7 ist ein Flag, das nur in einer Error-Response gesetzt werden darf und den Errorzustand anzeigt.

    Es gibt 19 von Standard im Detail definierte Funktionscodes, dazu ebenfalls 19 Usercodes, deren Struktur dem Anwender freigestellt ist. Der Code 0 ist illegal, alle weiteren bis 127 sind undefiniert.

    Die Bytes hinter dem Funktionscode sind bei den 19 Standardcodes inhaltlich festgelegt. Eine Modbus-Message kann maximal 254 Datenbytes enthalten.

    Der Modbusstandard geht von zwei Datentypen aus, Register (etwa uint16_t) und Coil (1 Bit).

    Ein Beispiel-Request ist z.B. das Abfragen mehrerer Register (von denen es unterschiedliche Typen gibt, aber hier ist das erstmal egal):

    02 03 00 01 00 04
    

    Hier wird Server Nummer 2 nach "Hold" Registern gefragt - Funktionscode 0x03 ist READ_HOLD_REGISTER -, und zwar ab Adresse 1 4 Register fortlaufend. Der FC 0x03 hat also zwei uint16_t-Parameter in MSB first-Reihenfolge.

    Der Server könnte antworten

    02 03 08 47 11 47 12 47 13 47 14
    

    Er schickt also die eigene Adresse, den Funktionscode und ein Längenbyte, dann die 4 uint16_t Registerinhalte.

    Das ist so im Standard für den Funktionscode 0x03 spezifiziert. Es gibt weitere Funktionscodes, die einen ähnlichen Requestaufbau haben, aber weitere Restriktionen zu den Parametern.

    Eine Besonderheit ist die Error-Response. Wenn der Request oben z.B. eine auf dem Server nicht vorhandene Adresse anspricht, meldet der Server dies mit

    02 83 02
    

    zurück. Das Errorflag im Funktionscode ist gesetzt, der Fehlercode ist 0x02, "Illegal address".

    Der Funktionscode bestimmt also, welche Bytes folgen können und welche Bedingungen sie einhalten müssen.

    Meine Library hat für die meisten der vordefinierten Funktionscodes Methoden, die die Meldung standardkonform aus übergebenen Werten aufbauen, oder eine empfangene Meldung nach Vorgabe des enthaltenen Funktionscodes in Werte zerlegen. Dabei sind manche Methoden für mehrere Funktionscodes passend, einige nur für genau einen. Das regelt der Typ des Funktionscodes (es gibt Typen für bestimmte Parametersignaturen, für generische und für die Illegalen).

    Es gibt 2 oder 3 sehr spezielle Codes mit ausufernden variablen Parametern, für die es keine dedizierte Methode gibt. Hier kann der Anwender dann aber generische benutzen, um einzelne Werte auszulesen usw.

    Die 19 Usercodes haben keine Formatfestlegung vom Standard, so dass auch hier die generischen Methoden eingesetzt werden.

    Lange Kurve zum ersten Post: die angesprochenen chinesischen Wechselrichter etc. benutzen Funktionscodes, die im Standard nicht definiert sind und von der Library deswegen mit einer automatischen Error-Response beantwortet werden. Sie sind aber so obskur, dass ich dafür keine Ausnahmen in die Library bauen werde.

    Deswegen sollen Anwender fallweise eigene Funktionscodes als legal definieren können, die die Library dann mit den generischen Routinen - oder sogar dedizierten, wenn die Parameter identisch zu einem definierten Funktionscode sind - bearbeiten können.

    Da die Funktionscodes in der Library bisher als enum vorgegeben sind und die Methoden mit dem enum-Typ für Funktionscodes aufgerufen werden, um Fehler zu vermeiden, sind diese zusätzlichen Codes eigentlich nur aufzunehmen, wenn der Anwender im Librarycode das enum erweitert und die Library neu übersetzt.

    Um das zu vermeiden, suche ich nach einer Möglichkeit, die nach der Definition der Library-bekannten Funktionscodes verbleibenden, eigentlich illegalen Codes durch den Benutzer explizit auf den Typ ihrer nicht-Standard-Meldungen umzudefinieren und mit einem Namen zu versehen.

    Was ich bisher zusammengebaut habe, erfüllt diesen Wunsch weitgehend schon, ich bin aber offen für jeden Verbesserungsvorschlag.



  • @Miq sagte in Check einer Konstanten auf Eindeutigkeit zur Compilezeit:

    Öh? Wenn ich nicht alle Details erwähnt habe, dann, um die Komplexität zu verringern, nicht, weil ich irgendwas verbergen wollte.

    Das hier meint DocShoe, und nein, das ist nicht meine Meinung.



  • @Miq sagte in Check einer Konstanten auf Eindeutigkeit zur Compilezeit:

    Da die Funktionscodes in der Library bisher als enum vorgegeben sind und die Methoden mit dem enum-Typ für Funktionscodes aufgerufen werden, um Fehler zu vermeiden, sind diese zusätzlichen Codes eigentlich nur aufzunehmen, wenn der Anwender im Librarycode das enum erweitert und die Library neu übersetzt.

    Um das zu vermeiden, suche ich nach einer Möglichkeit, die nach der Definition der Library-bekannten Funktionscodes verbleibenden, eigentlich illegalen Codes durch den Benutzer explizit auf den Typ ihrer nicht-Standard-Meldungen umzudefinieren und mit einem Namen zu versehen.

    Der Teil mit dem enum ist einfach zu beheben: gibt dem enum nen expliziten "underlying type":

    enum FunctionCode : uint8_t {
        FC_FOO = 0x11,
        FC_BAR = 0x22,
        // ...
    };
    
    void bar() {
        someFunction(static_cast<FunctionCode>(0x33));
    }
    

    Der Rest sollte auch nicht all zu schwer sein. In Frage kommende Lösungen hängen aber u.A. auch davon ab wie die Funktionen in deiner Library aussehen.

    Eine ganz einfache Möglichkeit könnte z.B. sein

    struct FooResult { ... };
    
    FooResult sendFooRequestCustom(FunctionCode code, int p1, int p2);
    
    inline FooResult sendFooRequest(int p1, int p2) {
        return sendFooRequestCustom(FC_FOO, p1, p2);
    }
    

    Benutzer mit "normalen" Geräten rufen dann sendFooRequest auf. Und Benutzer mit Geräten die einen "foo type" Request mit nem anderen Funktionscode haben können sendFooRequestCustom aufrufen und dabei den Funktionscode direkt angeben.



  • @Miq sagte in Check einer Konstanten auf Eindeutigkeit zur Compilezeit:

    @DocShoe sagte in Check einer Konstanten auf Eindeutigkeit zur Compilezeit:

    XY Problem

    Öh? Wenn ich nicht alle Details erwähnt habe, dann, um die Komplexität zu verringern, nicht, weil ich irgendwas verbergen wollte.

    Hast du nachgelesen was das XY Problem ist?



  • @hustbaer sagte in Check einer Konstanten auf Eindeutigkeit zur Compilezeit:

    Der Teil mit dem enum ist einfach zu beheben: gibt dem enum nen expliziten "underlying type":

    enum FunctionCode : uint8_t {
        FC_FOO = 0x11,
        FC_BAR = 0x22,
        // ...
    };
    
    void bar() {
        someFunction(static_cast<FunctionCode>(0x33));
    }
    

    Teil 1 (enum : uint8_t) ist schon so, Teil 2 mit dem static_cast funktioniert aber doch mit jeder beliebigen uint8_t-Variablen, ohne dass dabei ein gültiger Funktionscode entstehen muss?

    uint8_t irgendwas = 211;
    someFunction(static_cast<FunctionCode>(irgendwas));
    

    bringt dann was in someFunction()?

    Die oben skizzierte Variante würde durch

    someFunction(FunctionCode.code(irgendwas));
    

    immerhin alle illegalen Werte auf den Funktionscode 0 mappen.



  • @hustbaer sagte in Check einer Konstanten auf Eindeutigkeit zur Compilezeit:

    @Miq sagte in Check einer Konstanten auf Eindeutigkeit zur Compilezeit:

    @DocShoe sagte in Check einer Konstanten auf Eindeutigkeit zur Compilezeit:

    XY Problem

    Öh? Wenn ich nicht alle Details erwähnt habe, dann, um die Komplexität zu verringern, nicht, weil ich irgendwas verbergen wollte.

    Hast du nachgelesen was das XY Problem ist?

    Eigentlich ja - meint doch, ich verschleiere das eigentliche Library-Strukturproblem X, indem ich nach einer Lösung für das FunctionCode-Detail Y frage.

    Dahinter steckt natürlich, dass ich die Grundstruktur der Library nicht in Frage stellen will - sie funktioniert dafür zu gut. Ich würde eher den Support für die nicht-Standard-Codes ganz dünn oder gar weglassen, als dafür die normale Funktion zu gefährden.

    Ich experimentiere hier an und jenseits der Grenzen meines bisherigen Verständnisses von C++, deswegen kloppe ich das wieder in die Tonne, wenn es mir zu hoch, zu aufwendig oder sinnlos erscheint.

    Ich bin dennoch dankbar für Eure Beiträge, sie bringen mich immer wieder dazu, zu probieren und hinterfragen - kann ja auch unabhängig vom aktuellen Fall nur nützlich sein.



  • @Miq sagte in Check einer Konstanten auf Eindeutigkeit zur Compilezeit:

    @hustbaer sagte in Check einer Konstanten auf Eindeutigkeit zur Compilezeit:

    Der Teil mit dem enum ist einfach zu beheben: gibt dem enum nen expliziten "underlying type":

    enum FunctionCode : uint8_t {
        FC_FOO = 0x11,
        FC_BAR = 0x22,
        // ...
    };
    
    void bar() {
        someFunction(static_cast<FunctionCode>(0x33));
    }
    

    Teil 1 (enum : uint8_t) ist schon so, Teil 2 mit dem static_cast funktioniert aber doch mit jeder beliebigen uint8_t-Variablen, ohne dass dabei ein gültiger Funktionscode entstehen muss?

    uint8_t irgendwas = 211;
    someFunction(static_cast<FunctionCode>(irgendwas));
    

    bringt dann was in someFunction()?

    Naja da kommt dann 211 rein. Und das bringt das, dass die Header-Files deiner Library nicht geändert werden müssen um die 211 zu ermöglichen - trotz enum.

    Die oben skizzierte Variante würde durch

    someFunction(FunctionCode.code(irgendwas));
    

    immerhin alle illegalen Werte auf den Funktionscode 0 mappen.

    Sehe ich jetzt nicht als Vorteil.



  • @Miq sagte in Check einer Konstanten auf Eindeutigkeit zur Compilezeit:

    Hast du nachgelesen was das XY Problem ist?

    Eigentlich ja - meint doch, ich verschleiere das eigentliche Library-Strukturproblem X, indem ich nach einer Lösung für das FunctionCode-Detail Y frage.

    Der Fragende beim XY Problem will meist nix verschleiern oder geheim halten.

    Dahinter steckt natürlich, dass ich die Grundstruktur der Library nicht in Frage stellen will - sie funktioniert dafür zu gut. Ich würde eher den Support für die nicht-Standard-Codes ganz dünn oder gar weglassen, als dafür die normale Funktion zu gefährden.

    Ich will auch nicht die Grundstruktur der Library in Frage stellen. Ich wollte bloss wissen wie die aussieht. Weil man ohne das schwer konkrete Vorschläge machen kann.



  • @hustbaer Der Code steht hier. Für das Thema hier interessant sind ModbusTypeDefs.h - da steht das enum für die Funktionscodes drin - und ModbusMessage.h bzw. ModbusMessage.cpp.



  • @hustbaer sagte in Check einer Konstanten auf Eindeutigkeit zur Compilezeit

    Naja da kommt dann 211 rein. Und das bringt das, dass die Header-Files deiner Library nicht geändert werden müssen um die 211 zu ermöglichen - trotz enum.

    Die oben skizzierte Variante würde durch

    someFunction(FunctionCode.code(irgendwas));
    

    immerhin alle illegalen Werte auf den Funktionscode 0 mappen.

    Sehe ich jetzt nicht als Vorteil.

    Und in jeder dieser Funktionen muss ich dann checken, ob es ein gültiger Code ist? Nee.



  • Wieso prüfen?
    Du musst gar nix prüfen.
    Du machst einfach das was der Aufrufer vorgibt.


Anmelden zum Antworten