ubuntuusers.de

C++ und statische Arrays

Status: Gelöst | Ubuntu-Version: Ubuntu 10.04 (Lucid Lynx)
Antworten |

Dakuan

Avatar von Dakuan

Anmeldungsdatum:
2. November 2004

Beiträge: 6500

Wohnort: Hamburg

Ich benötige mal wieder Hilfe in Sachen C++.

In einer Funktion benötige ich eine Bewertungstabelle. In C ist das ja kein Problem, da packt man einfach irgendwo ein globales Array hin und fertig. In C++ ist sowas eher unüblich. Ich habe jetzt sowas versucht:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class BitFilter {
  private:
    static const char filter_tab[128] = {
/*  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, 1, /* 0. */
    0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 1, 1, /* 1. */
    0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 1, 1, /* 2. */
    0, 0, 0, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, /* 3. */
    0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 1, 1, /* 4. */
    0, 0, 0, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, /* 5. */
    0, 0, 0, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, /* 6. */
    0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1  /* 7. */
    };
    char *  bitfilter;
    int     mask;       // filter mask
  public:
    BitFilter( int n );
    ~BitFilter();
    void    setup( int n );
    int     filter( int b );
};
...
inline int
BitFilter::filter( int b ) {
    return bitfilter[ b & mask ];
}

was der Compiler mit

md01.cpp:210: Fehler: ISO-C++ verbietet Deklaration von »filter_tab« ohne Typ
md01.cpp:210: Fehler: ungültige Initialisierung innerhalb der Klasse des statischen Datenelements vom nicht eingebauten Typen »const int [128]«

quittiert.

Wie komme ich aus der Nummer wieder raus? Ist ein globales Array wirklich die einzige Lösung?

Panke

Anmeldungsdatum:
14. Oktober 2010

Beiträge: 133

Du musst das Feld innerhalb der Klassendeklaration deklarieren und in einer Übersetzungeinheit definieren.

Ungetestet. In der Klasse:

static const char filterTab[128];

In irgendeiner Übersetzungseinheit

/* static */ const char BitFilter::filterTab[128] = { ... }; // keine Ahnung, ob static hier nötig.

Dakuan

(Themenstarter)
Avatar von Dakuan

Anmeldungsdatum:
2. November 2004

Beiträge: 6500

Wohnort: Hamburg

Ich bin nicht sicher ob ich das jetzt richtig verstanden habe, aber irgendwie klingt das so ähnlich wie das, was eine bekannte Suchmaschine auch gefunden hat, und was ich doof finde.

Mein Ziel war eigentlich, das der Anwender dieses Filters (bzw. Bewertungsfunktion) keine Berührung mit dem Inhalt der Bewertungstabelle hat. Außerdem wollte ich verhindern, das der Compiler für jede neue Instanz eine neue Bewertungstabelle anlegt (die später möglicherweise auch noch größer werden kann). Deshalb wollte ich die Tabelle auch in der Headerdatei haben.

Wenn das so nicht geht, ist es möglicherweise besser das Ganze doch wieder als externes C Modul zu verwirklichen und dann mittels

extern "C" { ... }

einzubinden. Da muss der Anwender zumindest den Tabellenaufbau nicht kennen. Das "inline" Feature kann ich dann aber wohl vergessen. Einziger Trost ist, das der Verwaltungsoverhead für C Funktionen geringer ist als für C++ Funktionen.

Aber vielleicht hat ja jemand noch eine andere Idee. Da es irgendwann mal eine Echtzeitanwendung werden soll, geht es mir dabei vorwiegend um kurze Ausführungszeiten. Speicherplatz ist Nebensache.

Panke

Anmeldungsdatum:
14. Oktober 2010

Beiträge: 133

Dakuan schrieb:

Ich bin nicht sicher ob ich das jetzt richtig verstanden habe, aber irgendwie klingt das so ähnlich wie das, was eine bekannte Suchmaschine auch gefunden hat, und was ich doof finde.

In C++2003 kannst du statische Klassenattribute nur so wie beschrieben initialisieren.

Mein Ziel war eigentlich, das der Anwender dieses Filters (bzw. Bewertungsfunktion) keine Berührung mit dem Inhalt der Bewertungstabelle hat.

Hat er auch nicht, der Inhalt von *cpp-Dateien gilt gemeinhin als Implementierungsdetail.

Außerdem wollte ich verhindern, das der Compiler für jede neue Instanz eine neue Bewertungstabelle anlegt (die später möglicherweise auch noch größer werden kann). Deshalb wollte ich die Tabelle auch in der Headerdatei haben.

Genau das erhälst du, indem du sie als static in der Klasse deklarierst und in einer Übersetzungseinheit definierst.

Dakuan

(Themenstarter)
Avatar von Dakuan

Anmeldungsdatum:
2. November 2004

Beiträge: 6500

Wohnort: Hamburg

So, heute morgen sah die Sache viel klarer aus, dachte ich zumindest.

Wegen einiger irreführender Fehlermeldungen habe ich mich noch ein wenig im Kreis gedreht. Da wurde dann sogar der Konstruktor angemeckert, weil das Array nicht initialisiert wurde oder es kommt sowas:

md01.cpp:210: Fehler: Deklaration von »char BitFilter::filter_tab [128]« außerhalb einer Klasse ist keine Definition
/* static */ const char BitFilter::filterTab[128] = { ... }; // keine Ahnung, ob static hier nötig.

Das hat er mir ebenfalls um die Ohren gehauen:

md01.cpp:210: Fehler: »static« darf nicht bei der Definition (im Gegensatz zu Deklaration) eines statischen Datenelementes verwendet werden

Aber jetzt übersetzt cpp zumindest ohne irgendetwas anzunörgeln. Ob es auch funktioniert muss ich noch ausprobieren.

rklm Team-Icon

Projektleitung

Anmeldungsdatum:
16. Oktober 2011

Beiträge: 13205

Es ist, wie Panke schon gesagt hat: Deklaration in den Header, Definition in die C++-Quelle. Das ist auch ganz logisch, weil alle Benutzer der Klasse das Member kennen können müssen (könnte ja auch "public" sein). Die Definition mit Initialisierung liegt aber nur in einer Objekt-Datei.

1
$ g++ -c -Wall -ansi x.cxx

(Keine Fehler.)

Die Quelle sieht so aus (ich habe hier der Einfachheit halber den Header weggelassen:

1
2
3
4
5
6
7
8

// header
class X {
  static const char foo[];
};

// source
const char X::foo[] = { 'a', 'b' };

Alternativer Ansatz: Du erwähnst die Konstante gar nicht im Header und deklarierst sie in der Datei als "static":

Datei y.hxx

1
2
3
4
5
6
7

// header
class X {
  // nix
public:
  void test();
};

Datei y.cxx

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11

#include "y.hxx"

// source
#include <iostream>

static const char foo[] = { 'a', 'b' };

void X::test() {
  std::cout << foo[0] << std::endl;
}

Datei yy.cxx:

1
2
3
4
5
6
7
8

#include "y.hxx"

int main() {
  X x;
  x.test();
  return 0;
}

Testlauf:

1
2
3
4
5
6
7
8
$ rm *.o
$ g++ -c -Wall -ansi y.cxx yy.cxx
$ ls -l *.o
-rw-r--r-- 1 rklemme None 2.4K Oct 14 09:35 y.o
-rw-r--r-- 1 rklemme None  887 Oct 14 09:35 yy.o
$ g++ -Wall -ansi -o run *.o
$ ./run
a

Ciao

robert

Dakuan

(Themenstarter)
Avatar von Dakuan

Anmeldungsdatum:
2. November 2004

Beiträge: 6500

Wohnort: Hamburg

Ja, so wie ihr beide es vorgeschlagen habt, funktionierte es gestern Abend dann auch (nachdem ich auch die der eigentliche Funktion nachgebessert hatte).

Ich wollte mich auch gestern schon melden, aber ich war damit beschäftigt einen nervigen "segmentation fault' zu jagen. Da war ich wiedermal ein Opfer von Murphy geworden. (Wenn man einen Fehler durch ändern einer Variablen in 3 von 4 Modulen beseitigen kann, liegt der Fehler im 4. Modul.)

Alternativer Ansatz: Du erwähnst die Konstante gar nicht im Header und deklarierst sie in der Datei als "static":

Das wollte ich eigentlich zuerst auch. Hab's dann aber gelassen, weil globale Daten meist als schlechter Stil abgetan werden. Ich vergaß dabei dass "static" ja auch den Sichtbarkeitsbereich begrenzt.

Ich tendiere jetzt aber doch dazu den "alternativen Ansatz" zu verwirklichen.

Danke für die Hilfe.

tischbein

Avatar von tischbein

Anmeldungsdatum:
21. Juli 2008

Beiträge: 404

Du könntest auch std::list verwenden, welches sich genau wie ein statisches C-style Array verhält; d.h., die werte sind statisch, und werden auch statisch im Speicher gehalten, genau wie C Arrays. Der unterschied ist aber, dass std::list eine angenehmere syntax hat, und sich zudem leichter iterieren lässt. Ist natürlich dir überlassen.

Hier wäre z.B. ein beispiel:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

#include <list>

class Blah
{
    private:
        /* mit initializer_list, welche dann mit for(auto& elem: stuff)
           verwendet werden kann ... */
        std::list<int> stuff =
        {
            10, 20, 30, 40, 50, 60, 70, 80, 90
        };

    public:
        Blah()
        {
            /* C++11-style iteration */
            for(auto& elem: stuff)
            {
                /* irgendwas mit elem ... */
            }
        }
};

Und natürlich als C++11 kompilieren:

g++ -std=c++11 blah.cpp

Da kann man statisch-verwendete elemente auch gleich im Körper der klasse definieren, ohne diese dann noch extra ausserhalb des klassenkörpers definieren zu müssen, wie das ja bei C++98 der fall war.

Aber wie gesagt, ist dier überlassen. Wie gesagt, es würde sich genau wie statische C Arrays verhalten.

Lysander

Avatar von Lysander

Anmeldungsdatum:
30. Juli 2008

Beiträge: 2669

Wohnort: Hamburg

tischbein schrieb:

Du könntest auch std::list verwenden, welches sich genau wie ein statisches C-style Array verhält; d.h., die werte sind statisch, und werden auch statisch im Speicher gehalten, genau wie C Arrays. Der unterschied ist aber, dass std::list eine angenehmere syntax hat, und sich zudem leichter iterieren lässt. Ist natürlich dir überlassen.

Viel wichtiger ist eigentlich, dass die Zeitkomplexität beim Zugriff sich eben nicht wie bei Arrays verhält! Da das Ding sich eben nicht ändert, bleibt *das* als tatsächlicher Knackpunkt. Und da ist eine doppelt verkettete Liste bei einem Index-Zugriff denkbar schlecht geeignet. Wenn schon, sollte er std::vector nehmen - ich tendiere auch zu solchen Containern, jedoch mag es hier keine großen Vorteile dafür geben.

Wichtig ist dabei, dass man das algorithmische Laufzeitverhalten der Container stets im Blick hat und abhängig vom Anwendungsfall den optimalen wählt. Stumpf einen "Lieblings"-Container nutzen führt zu schlechtem Design...

Dakuan

(Themenstarter)
Avatar von Dakuan

Anmeldungsdatum:
2. November 2004

Beiträge: 6500

Wohnort: Hamburg

Danke nochmal für die Tipps, aber ich bin in C++ noch nicht so weit fortgeschritten, das ich das jetzt sofort umsetzen könnte. Ich denke eigentlich noch in C.

Wichtig ist dabei, dass man das algorithmische Laufzeitverhalten der Container stets im Blick hat und abhängig vom Anwendungsfall den optimalen wählt.

In der Tat hat hier die Ausführungszeit oberste Priorität, sonst hätte ich nicht die Tabellenlösung gewählt (die ist inzwischen schon auf 512 Einträge angewachsen).

Lysander

Avatar von Lysander

Anmeldungsdatum:
30. Juli 2008

Beiträge: 2669

Wohnort: Hamburg

Dakuan schrieb:

In der Tat hat hier die Ausführungszeit oberste Priorität, sonst hätte ich nicht die Tabellenlösung gewählt (die ist inzwischen schon auf 512 Einträge angewachsen).

Dann könntest Du immer noch std::array wählen, um eine hübschere API nutzen zu können 😉

Antworten |