statische Tabelle - private oder public ?



  • Ich bin gerade dabei ein größeres Projekt neu zu gestalten und kann von der neuen Version noch nichts kompilieren, da noch kaum etwas zusammenpasst.

    In der neuen Version soll eine Klasse mittels einer Tabelle anzeigen, welche Datenfelder für eine bestimmte Einstellung benötigt werden. Dies soll in einer Tabelle hinterlegt werden. Der Client soll also einen Zeiger auf diese Tabelle bekommen.

    In bisherigen Projekten hatte ich solche Tabellen immer als 'private' deklariert, da sie auch nur intern verwendet wurden. Beispiel:

    class BitFilter {
      private:
        const char *  bitfilter;// points to a partial section of filter_tab[]
        static const char filter_tab[512];
        int     mask;           // filter mask, defines the filter size
        int     bits;           // the bits to be filtered
      public:
        BitFilter( int n );
        ~BitFilter();
        void    setup( int n );
        int     filter( int b );
    };
    

    und: dann:

    const char
    BitFilter::filter_tab[512] = {
    /*  0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A, B, C, D, E, F         */
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /*  0. */
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, /*  1. */
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, /*  2. */
        0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 1, 1, /*  3. */
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, /*  4. */
        0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 1, 1, /*  5. */
        0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 1, 1, /*  6. */
    ...
        0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 1E. */
        1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1  /* 1F. */
    };
    

    nun möchte ich, das der Client eine solche Tabelle direkt lesen kann. Geht das wenn ich einfach nur in einer Abfragefunktion einen Zeiger zurück liefere oder muss dazu die Tabelle als 'public:' deklariert werden?

    Ich kann das momentan leider nicht ausprobieren, da der neue Code (ca. 15 Module) sich noch nicht kompilieren lässt (das wird wohl noch eine Woche dauern). Aber wenn ich da jetzt schon Probleme ein baue wird das noch länger dauern.



  • einwegflasche schrieb:

    Geht das wenn ich einfach nur in einer Abfragefunktion einen Zeiger zurück liefere oder muss dazu die Tabelle als 'public:' deklariert werden?

    Geht. protected/private auf Member ist quasi nur ein Verbot "den Namen auszusprechen". So lange man das nicht muss, kann man das Zeug ganz normal verwenden.

    Beispielsweise kann man sogar private "nested class" Typen verwenden, so lange man deren Namen nicht aussprechen muss. Beispiel:

    #include <utility>
    
    class Foo {
    private:
        class Bar {
        public:
            int getValue() { return 42;}
        };
    
    public:
        Bar makeBar() { return Bar(); }
    };
    
    int test() {
        Foo foo;
        auto bar = foo.makeBar(); // mit "auto" vermeiden wir "den namen Foo::Bar zu sagen" => OK
        return bar.getValue(); // auch hier müssen wir nicht "Foo::Bar" => auch OK
    }
    
    int test2() {
        // und wir brauchen nichtmal ein Foo objekt für den Spass...
        // Foo::Bar hat nen public ctor, und da wir den Typ Foo::Bar "beschreiben"
        // können ohne "Foo::Bar" zu sagen, nämlich als
        // "das was die Memberfunktion makeBar() eines Foo Objekts zurückgibt"
        // ...
        typedef decltype(std::declval<Foo>().makeBar()) Foo_Bar;
        Foo_Bar bar;
        return bar.getValue();
    }
    

    https://godbolt.org/g/6aaD1P



  • Du könntest ein privates std::array verwenden und const iteratoren zur Verfügung stellen.



  • Danke, ich werde das erstmal so umsetzen wie geplant um möglichst schnell wieder etwas lauffähiges zu bekommen.

    Das obige Beispiel stammt übrigens aus etwas, was mal ein Morsedecoder werden soll. Der Teil wird alls 5ms durchlaufen. Da, denke ich, ist der direkte Zugriff angebracht.



  • 5ms sind eine Ewigkeit. Und unterschätze nicht den Compiler. Was aktuelle Compiler so an inlining und darauf folgenden Optimierungen können ist schon recht beeindruckend.

    In dem Fall, also bei einem "gib mir nen Zeiger auf nen Table für Einstellung X", speziell wenn die Werte die X annehmen kann "dicht" sind (=man kann einfach in ein Array indizieren), kann man kaum viel falsch machen. OK, man könnte dynamisch Speicher anfordern, das sollte man vermutlich wirklich bleiben lassen. Ansonsten...

    Was vielleicht noch Sinn machen würde wäre Bitmasken statt einzelnen chars zu verwenden. Kommt aber natürlich drauf an wie die Dinger abgefragt werden - gibt schon Fälle wo ein char Array schneller sein wird.



  • 5ms sind eine Ewigkeit. Und unterschätze nicht den Compiler.

    Schon, aber das Kernstück soll auch auf einem Arduino laufen. Tatsächlich geht in der PC/GUI Version die meiste Prozessorleistung für die GUI drauf (da läuft dann noch ein Spectrogram). Da bin ich lieber etwas vorsichtiger.

    Kommt aber natürlich drauf an wie die Dinger abgefragt werden ...

    Das ist kein Geheimniss:

    inline int
    BitFilter::filter( int b ) {
        bits <<= 1;
        bits |= b & 1;
        return bitfilter[ bits & mask ];
    }
    

    Das ist eigentlich ein Schieberegister und ich will wissen, ob da mehr Nullen oder Einsen drin sind. Die Tabelle soll da die Zählschleife vermeiden.

    Du könntest ein privates std::array verwenden und const iteratoren zur Verfügung stellen.

    Das könnte für meinen aktuellen Fall eine Alternative sein. Da möchte der Client ein bestimmtes Objekt erstellt haben und lässt sich dafür erstmal die benötigen Bedienelemente mitteilen, um sie dann in der GUI anzuzeigen.



  • OK, das sieht halbwegs gut aus.
    Mir fällt jetzt keine Lösung ein bei der ich davon ausgehen würde dass sie gute Chancen hat schneller zu sein.

    Wobei ich Arduino nicht einschätzen kann. Keine Ahnung wie der Cache-mässig aufgestellt ist bzw. was da andere Operationen so kosten.

    Was ich aber nicht ganz verstehe ist wieso du von Aussen Zugriff auf die Tabelle geben willst. Und falls das wirklich Sinn macht, würde ich es in zwei Klassen aufsplitten. Eine Klasse die die Tabelle besitzt und bereitstellt, und die BitFilter Klasse die die Tabelle dann verwendet.
    Beides als öffentliche Funktionen in einer Klasse zu vereinen finde ich etwas unsauber.



  • Wobei ich Arduino nicht einschätzen kann...

    Das ist nur ein Mikrocontroller mit integriertem Flash Interface, USB, I/O-Pins und AD Wandler. Der hat nichtmal ein Betriebssystem. Aber man kann ihn einige Stunden mit einer 9V Batterie betreiben.

    Aber vergessen wir mal die BitFilter Klasse. Das war das erste Mal, das ich so etwas gemacht habe, weil ich gelesen hatte, das man solche Tabellen statisch macht, wenn alle Instanzen einer Klasse darin lesen sollen.

    Was ich jetzt brauche, ist eine Möglichkeit, der GUI zu sagen, welche von den ca. 10 Einstellelementen angezeigt werden müssen. Alle geht nicht wegen Platz und Übersicht.

    Das Bedien-Interface soll etwa so gehen: Der Benutzer wählt aus einer Liste einen Filtertyp aus und dann werden die entsprechenden Einstellfelder angezeigt. Und genau diese Daten sollen dann auch bei Auftragserteilung [GO-Button] abgeliefert werden.

    Da dachte ich, der Client bekommt einfach einen Zeiger auf eine Liste mit den entsprechenden ID-Nummern und arbeitet die ab.



  • OK.
    Wenn es nicht konkret um diesen Code geht, dann fürchte ich macht das die Sache nur noch weniger klar für mich.

    Ich verstehe glaube ich ganz ganz grob inetwa was du meinst. Nur das ist zu wenig um konkret einen Design-Vorschlag zu geben.

    Vielleicht könnte eine Aufteilung in folgende "Blöcke" sinn machen:

    1. Filterkoeffizienten-Sets/Tabellen (nur die Daten die der Filter zum Arbeiten braucht, evtl. noch mit ner ID oder nem Namen)
    2. Der Filter selbst
    3. "Metadaten" zu den Filterkoeffizienten-Tabellen die man in der GUI anzeigen kann
      Und natürlich das ganze wirkliche GUI Zeugs getrennt davon.

    (1) kann einfach ein haufen globaler Arrays/Objekte sein (const natürlich). Evtl. noch ein zusätzliches Array um alle Filterkoeffizienten-Sets zu "indizieren" (damit man drüberiterieren kann). Und evtl. eine Funktion dazu der man eine ID/Namen geben kann und die dann einen Zeiger auf das gewünschte Filterkoeffizienten-Set zurück gibt.

    (2) ist dann vermutlich ne Klasse und bekommt dann einfach einen Zeiger auf das Filterkoeffizienten-Set.

    Und wie (3) aussieht ist mMn. nicht so krass wichtig. Vermutlich irgend ein Daten-Container (struct, JSON File, ...) in dem ein plauderplauder Text zu dem Filterkoeffizienten-Set steht plus halt die ID. Wenn die GUI auch die rohen Koeffizienten anzeigen muss kann sie das ja machen indem sie die in (1) beschriebene Funktion aufruft.

    Aber wie gesagt: ich bin nicht sicher ob ich richtig verstanden habe was du konkret machen willst.



  • Also, bei der neuen Version ist es so, das die Filter Koeffizienten den Klient nichts angehen. So'n Filter ist jetzt nur noch eine schwarze Box. Aber natürlich ist vorgesehen, diese abfragen zu können. Das Ausgabeformat ist dann aber nicht JSON sondern C-Quelltext (damit ich das in andere Anwendungen einbauen kann).

    Es geht nur darum
    a) beim Erzeuger abzufragen, welche Einstellelemente angezeigt werden sollen
    b) anschlißend die benötigten Daten auch ab zu liefern.

    Das wollte ich über eine Tabelle lösen, die aber dem Erzeuger gehört.

    Ich habe aber noch keine gute Idee, wie der Client diese Daten dem Erzeuger mitteilen kann. Eine angebotene Tabelle ausfüllen geht wohl schlecht.

    Wenn die GUI auch die rohen Koeffizienten anzeigen muss ...

    Muss sie eigentlich nicht, aber sie kann die Filter Klasse damit beauftragen (später mal).

    In der alten Version liefert die Export Funktion:

    //  Filter type: IIR LP
    //  f1=0.1000  (800.0 Hz  SR=8000 Hz)
    //  ripple: 0.5%
    //  poles: 2
    //  tap_order=NORMAL
    int numtaps = 3;
    
    double b_coeff[] = {
           0x1.0507a80c46e5p-4, //  6.37280049E-02    0
           0x1.0507a80c46e5p-3, //  1.27456010E-01    1
           0x1.0507a80c46e5p-4  //  6.37280049E-02    2
    };
    
    double a_coeff[] = {
                        0x1p+0, //  1.00000000E+00    0
          0x1.31c1eca05b5bfp+0, //  1.19436530E+00    1
         -0x1.cc0f5a8db454bp-2  // -4.49277320E-01    2
    };
    

    Aber in der alten Version kennt der Client auch das Innenleben der Filter, was wohl nicht wirklich OOP ist.

    Die GUI ist eigentlich vorwiegend dazu da, den resultierenden Frequenzgang zu messen (nicht berechnen!), und die Sprungantwort zu zeigen. Dazu ist ein Wissen über das Innenleben nicht erforderlich.



  • einwegflasche schrieb:

    Aber in der alten Version kennt der Client auch das Innenleben der Filter, was wohl nicht wirklich OOP ist.

    Die GUI ist eigentlich vorwiegend dazu da, den resultierenden Frequenzgang zu messen (nicht berechnen!), und die Sprungantwort zu zeigen. Dazu ist ein Wissen über das Innenleben nicht erforderlich.

    Dann verstehe ich das:

    einwegflasche schrieb:

    nun möchte ich, das der Client eine solche Tabelle direkt lesen kann.

    nicht ganz 🙂

    Aber ist auch nicht so wichtig. Wenn du noch fragen hast, kannst du ja jederzeit fragen.


Log in to reply