Geht das eleganter?



  • Hallo,

    folgende Situation:

    Ich habe eine Klasse die aus einer Datei Daten liest. Die Datei kann aus bytes, floats etc bestehen. Gelöst habe ich das im Moment so: Die Klasse hat einen void* foo und der Ctor bekommt ein Flag übergeben, dass der Klasse sagt aus was die Datei besteht. Also im Pseudocode:

    if(m_dataType == TYPE_BYTE) foo = new BYTE[X];
    else if(m_dataType == TYPE_FLOAT) foo = new FLOAT[X];
    

    In den Memberfunktionen greife ich dann immer auf das Array zu und muss leider ständig Fallunterscheidungen machen und dann entsprechend casten:

    method() {
    ...
    if(m_dataType == TYPE_FLOAT) {
       y = (static_cast<FLOAT*>(foo))[index] * scaleY;
    } else if(m_dataType == TYPE_BYTE) {
       y = (static_cast<BYTE*>(foo))[index] * scaleY;
    }
    

    Das geht zwar aber ich finde es nicht sehr elegant (zum einen den void* und dann noch die ständigen ifs()). Könnte man das irgendwie eleganter machen?



  • Ich denke dass Templates das sind, wonach du suchst.



  • Und wie sieht das konkret aus? Die Klasse hat ja an sich nichts mit Templates am Hut.



  • SucherDerEleganz schrieb:

    Und wie sieht das konkret aus? Die Klasse hat ja an sich nichts mit Templates am Hut.

    Dann hast du den Sinn von templates nicht ganz verstanden. Mit einem Template kannst du eine Klasse generieren lassen, die deinen Anforderungen entspricht.

    template <class T>
    class foo
    {
    public:
        foo ():m_dataType (){}
        ~foo ()
        {
           if ( m_dataType )
               delete[] m_dataType;
        }
        void bar (int x)
        {
            m_dataType = new T[x];
        }
    private:
        T* m_dataType;
    };
    ...
    foo f<int> i; //T ist int
    foo g<float> f; // T ist float
    


  • @ drakon:
    Wieso verwendest du statt deines Beispiels nicht einfach Container? Oder ging es nur darum, die Einsatzmöglichkeit von Templates zu erläutern?



  • Eigentlich will ich nicht eine Klasse verwenden. Aber aus Interesse: Welche wäre der geeignetste Container aus der STL? Meine Arraygröße ist ja immer Konstant, sowas wie std::vector brauch ich also nicht)

    Mit einem normalen Array gibt es also keine Möglichkeit?



  • SucherDerEleganz schrieb:

    Mit einem normalen Array gibt es also keine Möglichkeit?

    Wenn du keine Klassen und somit keine Templates verwendet willst, geht das nur hässlich mit void -Zeigern, wie es halt üblich in C ist. Aus welchem Grund willst du Klassen meiden? Wir sind hier schliesslich in C++ und du hast nach einer eleganten Lösung gefragt...

    SucherDerEleganz schrieb:

    Welche wäre der geeignetste Container aus der STL? Meine Arraygröße ist ja immer Konstant, sowas wie std::vector brauch ich also nicht)

    Du könntest std::tr1::array (wenn nicht vorhanden, boost::array ) verwenden. Einen std::vector kannst du aber auch verwenden, wenn du die Funktionalität zur dynamischen Grössenänderung nicht nutzt.



  • Hm naja ok, also std::tr1::array. Aber auch da muss ich ja einen Typ angeben, also std::tr1::array<int, 4> oder std::tr1::array<float, 4> etc. Wie leg ich dann im Ctor ein tr1::array mit dem richtigen Typ an?



  • Darf ich fragen, was diese gemischten Werte darstellen? Vielleicht haben sie ja auch semantisch unterschiedliche Bedeutungen und es böte(n) sich evtl struct(s) an?



  • Naja, der Sinn ist folgender: Die Klasse liest eine raw-Datei ein, die entweder aus lauter INTs, oder floats oder anderen Typen besteht. Im Moment löse ich das wie im 1. Posting beschrieben so:

    class Foo {
    public:
       void* data;
    
    Foo(DATA_TYPE dataType) {
       if(m_dataType == TYPE_BYTE) data = new BYTE[X];
       else if(m_dataType == TYPE_FLOAT) data = new FLOAT[X];
       ...
    };
    

    Nur muss ich dann beim Lesen aufs Array immer eine Fallunterscheidung machen, um einen entsprechenden Cast auszuführen.

    Wie ich nun den void* data durch ein std::tr1::array ersetzen kann ist mir nicht ganz klar. Dazu brauch ich ja den Typen, aber den kenn ich ja nicht bei der Deklaration der Klasse. Ich müsste wohl also sowas schreiben:
    std::tr1::array<T> und Foo zu einer Template Klasse machen, oder? Aber das will ich auf keinen Fall, denn Foo hat mit Templates ja eigentlich nix am Hut 😕



  • Oh, sorry, dann hatte ich das falsch verstanden. Ich dachte, es würden gemischt floats, ints usw drinstehen.

    Vielleicht kann man sich der Lösung von einer anderen Seite nähern: Was machst du denn alles mit den Daten? Was für Funktionen wendest du darauf an, wie und wo modifizierst du die Daten oder liest welche aus?



  • Badestrand schrieb:

    Was machst du denn alles mit den Daten?

    Nur lesen, sonst nichts.



  • SucherDerEleganz schrieb:

    Badestrand schrieb:

    Was machst du denn alles mit den Daten?

    Nur lesen, sonst nichts.

    Komm schon, nicht so wortkarg, welchen Weg nehmen die Daten? Wohin werden sie weiterverarbeitet? Oder spannst du sie einfach nur ständig für Rechenoperationen ein, wie du im Anfangspost gezeigt hast:

    if(m_dataType == TYPE_FLOAT) {
       y = (static_cast<FLOAT*>(foo))[index] * scaleY;
    } else if(m_dataType == TYPE_BYTE) {
       y = (static_cast<BYTE*>(foo))[index] * scaleY;
    }
    

    ? Weil dann kannst du sie auch alle in einem double-Container speichern, da passen sowohl Bytes, als auch floats und ints super rein.



  • Ich greife eben ausschließlich lesend auf die Daten zu (und multiplizere den Wert dann, aber das is ja egal), so wie in dem kurzen Code-Ausschnitt. Eigentlich brauche ich also nur sowas:
    data[index] * bla;
    aber das geht ja leider nicht, da data vom Typ void* ist.

    Und ich denke das Speichern von BYTEs oder ints in einem double Array wäre nicht so ratsam 😉



  • Nexus schrieb:

    @ drakon:
    Wieso verwendest du statt deines Beispiels nicht einfach Container? Oder ging es nur darum, die Einsatzmöglichkeit von Templates zu erläutern?

    Nein. Er will ja je nach Datenyp, der in der Datei steht eine andere Klasse haben, was genau der Anwendungszweck von Templates ist.

    Die Klasse hat einen void* foo und der Ctor bekommt ein Flag übergeben, dass der Klasse sagt aus was die Datei besteht. Also im Pseudocode:

    Allerdings kommt die Vermutung auf, dass er das zur Laufzeit entscheiden möchte. Was dann die naheliegende Lösung über virtuelle Funktionen suggeriert.



  • google mal nach dem factory pattern, vielleicht gibt dir das einen Anreiz was du machen könntest. So spärlich wie du mit deinen Informationen umgehst kann dir leider auch nur spärlich geholfen werden.
    Ganz grob skizziert würde ich es so machen:

    - Untersuche, welche Programmteile abhängig von dem Datentyp sind
    - definiere eine Schnittstelle zu diesen Programmteilen, die Schnittstelle muss unabhängig von dem Typ sein
    - diese Schnittstelle deklarierst du als abstrakte Klasse
    - definiere ein klassentemplate (templateparameter ist der typ), das diese Schnittstelle implementiert
    - erstelle eine factory(-methode), die ermittelt welcher typ verwendet wird und die entsprechende Schnittstellenimplementation zurückliefert.

    damit hast du den typabhängigen teil gekapselt und fertig.



  • SucherDerEleganz schrieb:

    Und ich denke das Speichern von BYTEs oder ints in einem double Array wäre nicht so ratsam 😉

    Warum denn nicht, oder gibt's Speicherplatzprobleme? Wie viele Werte sind's denn ungefähr?



  • pumuckl schrieb:

    So spärlich wie du mit deinen Informationen umgehst kann dir leider auch nur spärlich geholfen werden.

    Hm, ich habe doch bereits alles genau beschrieben?!

    Aber ich kann ja gerne nochmal alles wiederholen:
    Das ist die Klasse:

    public:
       void* data;
    
    Foo(DATA_TYPE dataType) {
       if(m_dataType == TYPE_BYTE) data = new BYTE[X];
       else if(m_dataType == TYPE_FLOAT) data = new FLOAT[X];
       // Hier lese ich jetzt eine raw Datei und fülle damit das Array data
    };
    

    In Methoden von Foo greife ich dann lesend auf die Daten zu:

    if(m_dataType == TYPE_FLOAT) {
       y = (static_cast<FLOAT*>(foo))[index] * scaleY;
    } else if(m_dataType == TYPE_BYTE) {
       y = (static_cast<BYTE*>(foo))[index] * scaleY;
    }
    

    wobei ich eben jedesmal leider eine Fallunterscheidung machen muss.
    Wie ich das mit tr1::array machen könnte ist mir nicht wirklich klar (da muss ich ja einen Typ angeben aber den kenne ich erst zur Laufzeit). Und wie das mit dem Factory Pattern gehen soll is mir auch nicht klar - wie würde denn da der Ctor von Foo aussehen?

    pumuckl schrieb:

    Warum denn nicht, oder gibt's Speicherplatzprobleme?

    Das hätte doch nur Nachteile. Wenn ich ein double array anlege und das mit chars fülle, dann bedeutet data[1] ein offset von sizeof(double) und ich erreiche z.B. danmit gleich den 8 char. Die chars davor sind nicht erreichbar. Außerdem muss ich trotzdem wieder rumcasten, damit das Bitmuster richtig interpretiert wird.



  • SucherDerEleganz schrieb:

    Badestrand schrieb:

    Warum denn nicht, oder gibt's Speicherplatzprobleme?

    Das hätte doch nur Nachteile. Wenn ich ein double array anlege und das mit chars fülle, dann bedeutet data[1] ein offset von sizeof(double) und ich erreiche z.B. danmit gleich den 8 char. Die chars davor sind nicht erreichbar. Außerdem muss ich trotzdem wieder rumcasten, damit das Bitmuster richtig interpretiert wird.

    edit: Also einfach so (mit Textdatei values.txt):

    #include <iostream>
    #include <fstream>
    #include <algorithm>
    #include <vector>
    
    int main()
    {
    	// Datei öffnen
    	std::ifstream file( "values.txt" );
    	if ( ! file )
    		return 0;
    
    	// Datei einlesen
    	std::vector<double> arr;
    	std::copy( std::istream_iterator<double>(file), std::istream_iterator<double>(), std::back_inserter(arr) );
    
    	// Werte ausgeben
    	for ( size_t i=0; i<arr.size(); ++i )
    		std::cout << arr[i] << std::endl;
        // Oder auch:  std::copy( arr.begin(), arr.end(), std::ostream_iterator<double>(std::cout,"\n") );
    }
    


  • SucherDerEleganz schrieb:

    Hm, ich habe doch bereits alles genau beschrieben?

    Mitnichten. Und der Nick passt auch nicht zu den geposteten. Den "Eleganz" suchst du nicht, dann würdest du dein gesamtes Design nochmal überdenken und nicht so verbohrt auf deinen unsauberen Codeschnipsel sein. Alles was ich von dir gelesen habe ist eher die Suche nach Effizienz was aber meistens der Eleganz zu wieder läuft.

    cu André


Anmelden zum Antworten