Geht das eleganter?



  • drakon schrieb:

    ...
    Ich habe mich dich immer jünger vorgestellt. 🙂 (Was wohl am namen liegt, da ich nur jugendliche Simons kenne. :))...

    Um das mal klarzustellen: Ende des Jahres werde ich 40, bin seit 13,5 Jahren verheiratet und habe zwei 10jährige Töchter (also insgesamt 3 großartige Mädels im Haus, die mir immer wieder Gelassenheit schenken für so Heißsporne wie den Threadersteller hier 😉 😃 ).

    Ich kenne sonst auch nur recht jugendliche "Simons" und war meine ganze Kindheit hindurch eigentlich ganz stolz auf meinen Exotennamen (es gab sogar Leute, die fragten, ob ich jüdisch sei). Dafür habe ich leider auch nicht das "Überhören" gelernt, wenn Namensvettern gerufen werden, wie es wohl Franks, Thomasse und Claudias entwickelt haben. Wenn jetzt ein Mutter ihren "Siiiiiiimon" ruft, komme ich auch oft angelaufen (tja, ich weiß schon, warum Elten ihren Kindern peinlich sind 😉 ).

    Gruß,

    Simon2.



  • SucherDerEleganz schrieb:

    Simon2 schrieb:

    Diese Anforderung ist mir bei Dir bislang noch nicht aufgefallen.

    ...Auf SEITE 1 des Threads ist in meinem Code Sample klar ersichtlich, dass void* data; ein MEMBER der Klasse ist...

    Aha! Ich kann Dir ach genau sagen, wo mein Mißverständnis lag:
    Lies mal laut:

    SucherDerEleganz schrieb:

    ...Ich brauche ja einen Container/Array als Member, das mir die Daten aus der Datei hält ...

    (so habe ich es verstanden)
    und

    SucherDerEleganz schrieb:

    ...Ich brauche ja einen Container/Array als Member, das mir die Daten aus der Datei hält ...

    (so hast Du es anscheinend gemeint)
    Das sollte ein wenig belegen, warum man schriftlich etwas ausführlicher formulieren muss als in der "Sprechsprache".

    @Topic:
    1.) Ich sehe nicht, wo meine Lösung einem Einsatz innerhalb einer Klasse widersprechen sollte. Dann packst Du die Funktionen eben in eine Klasse, aber das bekommst Du schon hin. Du kannst Dich auch noch entscheiden, ob Du für jeden Typ einen eigene Klasse anlegen (sprich: Klassentemplate) oder innerhalb der einen Klasse die "fachlichkeit()" typspezifisch machen möchtest (wie ich es oben getan habe)
    2.) Ich sehe auch nicht, warum diese Anforderung besteht; die Implementierung in nerhalb einer Klasse ist ein eher technisches Implementierungsdetail, das mit den fachlichen Anfordeungen eher weniger zu tun hat.

    Aber nun gut:
    Entwurf mit Klassentemplate (1.)

    template <typename T>
    struct foo {
       vector<T> v;
       void fachlichkeit() {
          y = v[index] * scaleY; // keine Fallunterscheidung mehr nötig
       }
       foo(size_t) : v(size) {}
    };
    
    int main() {
    ...
       switch(dataType) {
       case TYPE_BYTE: foo<char>(X).fachlichkeit(); break
       case TYPE_FLOAT: foo<float>(X).fachlichkeit(); break
    ...
    

    (Kann natürlich auch erstmal nur das foo-Objekt erzeugen (mit new und interface wie von Nexus beschrieben) und dann woanders fachlichkeit() aufrufen)

    ... und einer für 2.)

    struct foo {
       size_t s;
       template <typename T>
       void fachlichkeit() {
          vector<T> v(s);
          y = v[index] * scaleY; // keine Fallunterscheidung mehr nötig
       }
       foo(size_t) : s(size) {}
    };
    
    int main() {
    ...
       foo f(X);
       switch(dataType) {
       case TYPE_BYTE: f.fachlichkeit<BYTE>(); break
       case TYPE_FLOAT: f.fachlichkeit<FLOAT>(); break
    ...
    

    Ein Pferdefuß von Nexus' reiner "virtual-Lösung" ist, dass nicht generisch auf die Daten in der Liste zugegriffen werden kann. Ich weiß zwar, dass alle ein "idata"-Objekt beinhalten, aber wenn ich konkret mit einem arbeiten möchte, weiß ich nicht, welchen Typ sie haben ... und eine "generische Zugriffsfunktion" kann es auch nicht geben aufgrund der "Vererbungssemantik" (=gleiche Funktionssignatur) in C++.
    Aber man könnte sie mit meinen Ansätzen kombinieren.

    Nochmal ein Tipp zu Deiner Kommunikation: Wenn Du
    1.) Nicht sofort verbal um Dich schlägst, wenn Dir etwas nicht passt
    2.) eigene Begrenztheit (wie sie jeder Mansch hat) und die Existenz von Mißverständnissen ("Warum Boshaftigkeit voraussetzen, wo Irrtum oder DUmmheit es genauso erklären?", sagt ein Kollege immer so schön) mit in Deine Überlegungen einbeziehst und
    3.) Dich nicht nur auf die Dinge, die Dir nicht passen stürzt, sondern auch mal sagst, wo Du was verstehst, was Dir gefällt, ....
    dann kommst Du im Leben, im Internet und erst Recht hier im Forum deutlich schneller und stressfreier zum Ziel (ich habe nämlich deutlich mehr geschrieben als die paar Schnipsel, auf die Du eingehst).

    So - nun bringe ich meine Tochter zum Volleyballspiel.

    Schönen Tag noch,

    Simon.



  • Ein Pferdefuß von Nexus' reiner "virtual-Lösung" ist, dass nicht generisch auf die Daten in der Liste zugegriffen werden kann. Ich weiß zwar, dass alle ein "idata"-Objekt beinhalten, aber wenn ich konkret mit einem arbeiten möchte, weiß ich nicht, welchen Typ sie haben ... und eine "generische Zugriffsfunktion" kann es auch nicht geben aufgrund der "Vererbungssemantik" (=gleiche Funktionssignatur) in C++.

    Ich denke mal, dass du mich gemeint hast und nicht Nexus. 😉

    Das mit dem Zugriff ist mir schon klar, aber im Normalfall tendieren die Leute dazu zu meinen, dass sie wissen müssen, mit was sie es zu tun haben, als sich einfach auf die Schnitstelle zu verlassen.
    Schlussendlich macht man das ganze mit abstrakter Klasse usw. ja darum, weil die Objekte eine einheitliche Aufgabe haben, aber jeweils ev. etwas anderes machen müssen. Wenn aber keine einheitliche Schnittstelle gefunden werden kann, dann hat auch keine Vererbung stattzufinden.
    (Nichtsdesto trotz ist deine Lösung natürlich eine gute Möglichkeit das zu erzwingen. Nur muss man sich dann fragen, ob das so nötig ist. Und mir fällt kein gutes Beispiel ein, wo es das wirklich ist. 😉 )



  • drakon schrieb:

    ...Ich denke mal, dass du mich gemeint hast und nicht Nexus. ;)...

    😮 😮 😮
    KREISCH!!
    Natürlich - sorry.

    drakon schrieb:

    ...Das mit dem Zugriff ist mir schon klar, aber ...

    Hast natürlich Recht, aber ich wollte den Threadstarter da vorsichtshalber mal darauf hinweisen. Mir schien es halt so, als ob er die Klasse im Wesentlichen als "Verpackung für seine Daten" haben, deren eigentliche Verarbeitung aber außerhalb haben wollte. Und dahin kommt er eben mit Deinem Ansatz nicht (ungefrickelt)...
    Letztlich wollte ich nur darauf hinweisen, dass da noch Arbeit vor ihm liegt, wenn er diesen Weg beschreitet.

    Gruß,

    Simon2.



  • Simon2 schrieb:

    int main() {
    ...
       switch(dataType) {
       case TYPE_BYTE: foo<char>(X).fachlichkeit(); break
       case TYPE_FLOAT: foo<float>(X).fachlichkeit(); break
    ...
    

    Ich habe nicht den ganzen Thread verfolgt. Aber bei dem obigen Programmcode stellen sich mir ein paar Fragen

    • Es geht nicht um das Double Dispatch Pattern?
    • Wir sind im C++ Forum?

    Wenn beide Fragen mit "Ja" zu beantworten sind, stellt sich gleich Frage Nummer drei:

    • Darf ich die Kotztüte hervorholen?


  • ~john schrieb:

    • Es geht nicht um das Double Dispatch Pattern?
    • Wir sind im C++ Forum?
    • Darf ich die Kotztüte hervorholen?

    Vielleicht, ja, wenn du es umbedingt willst...

    Ich bin noch immer der Auffassung das dieser Thread nichts mit Eleganz zu tun hat, und das wir - weil wir nicht die Hintergründe vom OP kennen, nur hier rätseln können. Vermutlich könnte man das alles deutlich eleganter lösen wenn wir nicht nur die Vorgabe wie der Code besser wäre, sondern auch das Umfeld kennen würden (Ich tippe nach wie vor an einen Designfehler, oder zumindestens auf etwas das man - zumindestens in C++ anders angeht).

    cu André



  • ~john schrieb:

    ...Darf ich die Kotztüte hervorholen?...

    Keine Sorge. Da Frage 1 bereits mit Nein zu beantworten ist, stellt sich diese hier nicht mehr.
    Es geht hier um einen einfachen Dispatch (reine Erzeugung) anhand von Typinformationen, die erst zur Laufzeit aus einer Datei gelesen werden.

    Das Lesen den ganzen Threads wäre vllt. keine schlechte Idee gewesen. :p 😉 😃
    (dann wäre vllt. auch klar geworden, dass es mir im Wesentlichen um die Möglichkeiten von templates und ihre Verknüpfung mit Laufzeitbedingungen ging)

    Gruß,

    Simon2.



  • Ganz ehrlich, alle Vorschläge sind mit Kanonen auf Spatzen schießen.
    Den ganzen von Simon2 geposteten Code kann ich so nicht verwenden. WIE (in welcher DATENSTRUKTUR - sprich: was nun als Member statt dem void* stehen soll) ist mir noch immer nicht klar.

    Dann der Code von drakon. Er ist wenigstens der einzige, der mal konkret sagt durch was ich nun void* ersetzen soll. Aber für jeden Datentyp eine eigene Klasse ableiten? Nur weil ich aus einer Datei Daten einlesen will? ...
    Und der Sinn von dem Code ist auch zweifelhaft. Du legst eine liste von IDataType* an. Wieso eine Liste, wenn du eh nur 1 Objekt reinlegst. Und wenn ich diese liste dereferenziere, dann bekomm ich ein IDataType*. Was soll ich damit? Ich will einfach v[index] schreiben und dann den richtigen Typen haben.

    Ich wollte einfach für diesen kleinen Problembereich (void* ersetzen bei verschiedenen Typen) eine simple Lösung - irgend ne andere Struktur oder sonst irgendwas mit 2-10 Zeilen. Offenbar ist das nicht möglich. Naja, drauf geschissen, das sind jetzt 3 Zeilen Code und ich lass den void*.

    Und so arrogante Aussagen wie sie von ~john könnt ihr euch auch sparen. Thread nicht mal lesen, falschen Vorschlag machen und zusätzlich auch arrogant von oben herab urteilen. Mit Double Dispatching hat das hier herzlich wenig zu tun - hier geht es im Kern nicht um Dynamisches Dispatching von Funktionen sondern um die richtigen Datenstruktur.

    Es gibt in C++ in vielen Fällen bessere Möglichkeiten als void*, aber es gleich generell zum "kotzen" finden, ist lächerlich. Als ob alles immer gut oder schlecht sein muss. Wenn der Code perfekt arbeitet und tief in einer lowen Klasse weggekaselt ist die man nie mehr anrühren muss, ist das absolut ok.



  • Also ich hab mir nicht den ganzen Thread durchgelesen, aber ich würde das vielleicht so machen:

    class IFoo
    {
    public:
        virtual void Bar() = 0; // die "eine Funktion" wo der Datentyp einen Unterschied macht
    };
    
    template <class T> class Foo : public IFoo, private boost::noncopyable
    {
    public:
        Foo(istream& is, size_t count)
        {
            // ...
            m_data.resize(count);
            is.read(reinterpret_cast<char*>(&m_data[0]), count * sizeof(T));
            // ...
        }
    
        virtual void Bar()
        {
            // ...
            // was auch immer "v" ist...
            v[index].SplineB.y = m_data[index] * m_scaleY;
        }
    
    private:
        std::vector<T> m_data;
        // ...
    };
    
    class VarFoo : private boost::noncopyable
    {
    public:
        template <class T> VarFoo(boost::type<T>, istream& is, size_t count) :
            m_foo(new Foo<T>(is, count))
        {
        }
    
    private:
        boost::scoped_ptr<IFoo> const m_foo;
    };
    

    Bei bloss 2 Datentypen ist das vermutlich mehr Code als die "if Orgien" Version, je mehr Datentypen es sind desto besser schneidet die Template-Version ab.
    Vor allem spart man sich die ganzen Fallunterscheidungen zu tippen, und vor allem auch zu warten. Wenig ist lästiger als eine 1-Zeilen änderung an 10 Stellen machen zu müssen weil man sinnlos Code dupliziert hat.



  • Und der Sinn von dem Code ist auch zweifelhaft. Du legst eine liste von IDataType* an. Wieso eine Liste, wenn du eh nur 1 Objekt reinlegst. Und wenn ich diese liste dereferenziere, dann bekomm ich ein IDataType*. Was soll ich damit? Ich will einfach v[index] schreiben und dann den richtigen Typen haben.

    Ich meine mal irgendwo was gelesen zu haben, dass das ein Array sein soll?! (OK. std::vector o.ä. wäre angebrachter gewesen.)

    Ansonsten, wenn du nicht zufrieden bist mit dem, was wir hier zeigen, dann belass es doch einfach bei void.. Die Sprachmittel sind ja schliesslich da, um genutzt zu werden. Und wenn du das Gefühl hast, dass void* das einzig richtige ist, dann kannst du das ja auch ruhig benutzen. (Was nicht bedeutet, dass ich das auch machen würde.)

    Ansonsten versuch doch mal ein exaktes Beispiel zu bringen, dass mit void* genau das macht, was du willst. (mit der dazugehörigen Datei zum einlesen usw.). Dann wissen wir nämlich genau, was du willst und können dir auch genau darauf eine Antwort geben. Ansonsten verschwendest du/wir nur unsere Zeit und am Schluss bist du nicht glücklich und wir haben uns einen Haufen Arbeit für nichts gemacht..



  • SucherDerEleganz schrieb:

    Ganz ehrlich, alle Vorschläge sind mit Kanonen auf Spatzen schießen. ...

    Äh - ich sehe hier keine "Kanonen".
    Der Code, der Dir hier vorgelegt wird, ist weder länger noch komplizierter noch langsamer noch .... als Deiner. Er nutzt lediglich mehr von den C++-Sprachmitteln (aber alles keine Zauberei).

    Und Dein Problem ist auch kein "Spatz".

    SucherDerEleganz schrieb:

    ...WIE (in welcher DATENSTRUKTUR - sprich: was nun als Member statt dem void* stehen soll) ist mir noch immer nicht klar....

    Das ist Dein Problem: Du willst kein anderes (eleganteres) Design, sondern lediglich einen "Programmierkniff", den Du bei Dir reinkopieren kannst, ohne grundlegend etwas zu ändern.
    Da allerdings die von Dir (zu Recht) beklagte "Uneleganz" kein kleiner Schönheitsfehler ("Spatz") ist, wirst Du das, was Du willst, nicht bekommen.

    Oder anders gesagt:

    SucherDerEleganz schrieb:

    ...Geht das eleganter?...

    Nein.
    (jedenfalls nicht unter den von Dir gemachten (und von mir größtenteils als unnötig empfundenen) Vorgaben)

    ... irgendwie habe ich den Eindruck, dass Du das eigentlich die ganze Zeit hören wolltest.

    Gruß,

    Simon2.



  • Man könnte zusammenfassend sagen:

    Ja, es geht eleganter. Aber solang du darauf bestehst dass alles so bleibt wie es ist, gehts eben nicht eleganter.



  • SucherDerEleganz schrieb:

    Und so arrogante Aussagen wie sie von ~john könnt ihr euch auch sparen.

    Arrogant ist hier nur eine Person. Es wird keinerlei Code herausgerückt, so daß es schlicht weg nicht möglich ist einen sinnvollen Vorschlag zu machen. Wenn man void* vermeiden will, muß man das Design entsprechend anpassen und dazu muß man das Programm entsprechend tiefgreifend ändern. Der Hinweis auf Double Dispatch ist insofern von Belang, weil dies der einzige sinnvolle Grund ist noch auf switch case mit TypeIDs in C++ auszuweichen. Macht man es trotzdem hat man einen Designfehler produziert. Bei Altlasten mag das durchaus sinnvoll sein es weiter zu behalten, aber das kann nicht der Regelfall für neuen Code sein.



  • pumuckl@logged_off schrieb:

    Man könnte zusammenfassend sagen:

    Ja, es geht eleganter. Aber solang du darauf bestehst dass alles so bleibt wie es ist, gehts eben nicht eleganter.

    Oder noch kürzer: "besser" ε { "anders" }
    😉



  • Was diskutiert ihr denn überhaupt mit so einer Knallerbse? Eure ganzen Tipps sind doch echt Perlen für die Säue. Ich wurde mal sagen, dass der gute™ TO sich inzwischen eindeutig als Troll™ geoutet hat.



  • Simon2 schrieb:

    Es geht hier um einen einfachen Dispatch (reine Erzeugung) anhand von Typinformationen, die erst zur Laufzeit aus einer Datei gelesen werden.

    Im Falle von zwei Typen ist ein "Dispatch" schon overkill, eine Fallunterscheidung tät es da schon. Wenn man sich allerdings die Mühe macht extra Klassen zu erzeugen und dann Switch&Case mit TypID benutzt, dann vergeht mir der Spaß. Denn in so einem Fall kann man das gleich sauber und elegant machen, d.h. mittels Polymorphie, dafür ist sie ja in C++ vorhanden.

    Simon2 schrieb:

    Das Lesen den ganzen Threads wäre vllt. keine schlechte Idee gewesen. :p 😉 😃

    Da der Thread nur wenig konkrete Information vom Urheber desselben beinhaltet, habe ich mich auf das Querlesen beschränkt. Ich weiß, bis jetzt nicht was sein konkretes Problem ist. Abgesehen davon, daß er Fallweise Floats und Doubles aus einer Datei einlesen muß, wird er nicht nämlich nicht sonderlich konkret.



  • ~john schrieb:

    ...Im Falle von zwei Typen ist ein "Dispatch" schon overkill...

    Ich hatte das schon so verstanden, als ob es um eine deutlich größere Anzahl von Typen geht.

    Hmmmm - ich bin ja auch ein grooooßer Freund von Polymorphie und ein ebenso intensiver Gegner von Typ-IDs (und dezugehörigem Frickelcode).
    Aber wie hättest Du es denn (ohne Typkennzeichner und entsprechendes switch) die Erzeugung von Objekten unterschiedlichen Typs aus einem flat file gelöst ?
    Natürlich kann man das auslagern in ein "Creator", an dem sich Factories registrieren ... aber ohne irgendein Typkennzeichen auskommen ? Da fällt mir gar nichts zu ein.

    Gruß,

    Simon2.



  • Simon2 schrieb:

    Aber wie hättest Du es denn (ohne Typkennzeichner und entsprechendes switch) die Erzeugung von Objekten unterschiedlichen Typs aus einem flat file gelöst ?

    Mit überladenen Funktionen für die jeweiligen Typen. Einlesen tut man dann in den entsprechenden Typen, und dann ruft man z.B. eine put Funktion auf. Welche put Funktion dann konkret benutzt wird, entscheidet der Typ der Einlesevariable.



  • Tachyon schrieb:

    ...Mit überladenen Funktionen für die jeweiligen Typen. Einlesen tut man dann in den entsprechenden Typen, und dann ruft man z.B. eine put Funktion auf. Welche put Funktion dann konkret benutzt wird, entscheidet der Typ der Einlesevariable.

    Sorry, hast Du da mal ein Beispiel für ?
    (ich will jetzt nicht in die Fußstapfen des TOs treten, aber es interessiert mich wirklich)

    Ich sage jetzt mal so: Nach meinem Verständnis greifen virtuelle Funktionen erst bei bereits erstellten Objekten (klar - ohne vtable geht's halt nicht) ... aber die Entscheidung, welches Objekt nun erzeugt wird, muss doch vorher stattfinden.
    NACH der Erzeugung braucht man natürlich keinen Typkennzeichner mehr - da weiß jedes Objekt selbst, was für ein Typ es ist (bzw. wie es die virtual-Schnittstelle zu bedienen hat) - aber davor ?

    Oder anders ausgedrückt: Was soll in dem File stehen und wer liest das ein ?

    Gruß,

    Simon2.



  • Simon2 schrieb:

    ...

    Ne, ich rede hier nicht von Polymorphie und spätem binden.
    Ich meinte frühes binden, so ähnlich, wie bei std::ostream:

    void CC::put(int a);
    void CC::put(float a);
    void CC::put(double a);
    //...
    
    CC c(/*...*/);
    //Entscheide, was gelesen wird, in diesem Fall float...
    float input;
    c.put(input); //CC::put(float a)
    

    Irgendwie so.


Anmelden zum Antworten