Designfrage - Templates ja/nein/vielleicht?



  • HumeSikkins schrieb:

    Meinst du echt es müssen mehrere Filter sein oder reicht vielleicht auch sowas wie ein Composer (à la boost::compose bzw. boost::bind)?

    Jo, daran habe ich nicht gedacht. Die Frage ist allerdings: wie läuft das mit dynamischen Filtern - wenn die Filter nicht zur Compilezeit feststehen -> garnicht, oder?

    Die Unterstützung mehrerer Filter würde das Ganze auf jeden Fall doch deutlich komplizieren.

    Ja, allerdings würde es die Verwendung vereinfachen - denn sonst muss ich mir sehr oft selber Filter schreiben die dann nur

    return foo("bla") && bar("bla");
    

    machen... das finde ich n bisschen doof. Es sei denn natürlich man könnte zur Laufzeit Filter zusammenfügen - mit and und or - dann noch ein not und du hast die perfekte Flexibilität.

    Ich danke da vorallem an Programme wo der User diese Filter angibt. Und da wäre so eine Flexibilität Goldwert.



  • Mehrere Filter könnte man gut realisieren, wenn die Filter selber streams sind und ein Filter auf dem anderen arbeitet. So ein bisschen ähnlich wie die Streams in Java.

    Also nicht

    str.addFilter(filter1);
    str.addFilter(filter2);
    

    sondern

    stream myFilteredStream(new ExtensionFilterStream(".cpp", new TempFilterStream(myDirStream) ));
    

    Leseoperationen delegieren dann die Filter entsprechend an ihrem Stream, den sie verwalten, so entsteht eine richtig schöne Verkettung und jeder filtert sein Zeug heraus.
    Ich sehe nur das Problem mit der Resourcenverwaltung. Vielleicht kann man das über autopointer schön machen. Hmmm müssen die Dinger überhaupt auf dem Heap liegen?

    Die erste Variante finde ich hässlich und unflexibel ist sie ja auch. Bei Festplattenzugriffen ist die Performance eh schon hinüber.



  • HumeSikkins schrieb:

    @Shade
    Meinst du echt es müssen mehrere Filter sein oder reicht vielleicht auch sowas wie ein Composer (à la boost::compose bzw. boost::bind)?
    Man könnte ja sowas wie and und or-Adapter bauen:

    bool filter1(const string& e);
    bool filter2(const string& e);
    
    // wird zu if (filter1(e) && filter2(e))
    dirstream s(".", make_filter(and(filter1, filter2)));
    
    // wird zu if (filter1(e) || filter2(e))
    dirstream s2(".", make_filter(or(filter1, filter2)));
    

    Über die Syntax könnte man ja noch nachdenken.

    Die Unterstützung mehrerer Filter würde das Ganze auf jeden Fall doch deutlich komplizieren.

    Ich finde die Idee sehr gut. Vor allem spart man sich das temporaere
    Speicher + durchsuchen eines Verzeichnisses, falls man nur einen Filter setzen
    kann. Da du hier boost ansprichst, man koennte zur Implementierung der
    Filter doch boost::regex einsetzen. Das vereinfacht nicht nur dir die
    Implementierung, sondern auch dem Anwender die Anwendung. Und mehrere Filter
    zu durchlaufen sollte doch mit ner einfachen list moeglich sein, oder nicht?

    Wahrscheinlich stelle ich mir das viel zu einfach vor.

    mfg
    v R



  • die list hat ein problem: filter werden linear abgearbeitet, dh wenn der langsamste filter vorne steht, wird dein programm von vornherein langsam werden, da der filter immer aufgerufen wird, egal was passiert.

    da find ich die konstruktion mit and/or und not viel hilfreicher 🙂

    and(name("*.txt"),wordInFile("hallo"));
    


  • otze schrieb:

    die list hat ein problem: filter werden linear abgearbeitet, dh wenn der langsamste filter vorne steht, wird dein programm von vornherein langsam werden, da der filter immer aufgerufen wird, egal was passiert.

    da find ich die konstruktion mit and/or und not viel hilfreicher 🙂

    Ja das stimmt, daran hab ich gar nicht gedacht.

    mfg
    v R



  • Shade Of Mine schrieb:

    HumeSikkins schrieb:

    Meinst du echt es müssen mehrere Filter sein oder reicht vielleicht auch sowas wie ein Composer (à la boost::compose bzw. boost::bind)?

    Jo, daran habe ich nicht gedacht. Die Frage ist allerdings: wie läuft das mit dynamischen Filtern - wenn die Filter nicht zur Compilezeit feststehen -> garnicht, oder?

    Dynamisch:

    class IFilter
    {
    public:
        virtual bool operator()(const std::string& e) = 0;
        ...
    };
    
    class AndFilter : public IFilter
    {
    public:
        AndFilter(IFilter* f1, IFilter* f2)
            : f1_(f1)
            , f2_(f2)
        {}
        bool operator()(const std::string& e) {return (*f1_)(e) && (*f2_)(e);}
    private:
        std::auto_ptr<IFilter> f1_;
        std::auto_ptr<IFilter> f2_;
    };
    
    class NotFilter : public IFilter
    {
    public:
        NotFilter(IFilter* f1)
            : f1_(f1)
        {}
        bool operator()(const std::string& e) {return !(*f1_)(e);}
    private:
        std::auto_ptr<IFilter> f1_;
    };
    ...
    

    Allerdings würde ich hier dann doch Templates verwenden und die einzelnen Filter "by value" an And, Or und Not binden. Das endgültige Funktionsobjekt würde ich dann durch einen Prädikat-Adapter zu einem IFilter-kompatiblen Objekt machen. So habe nur eine dynamische Allokation.
    Alles andere erscheint mir für den Anfang etwas overkill zu sein.

    Mehrere Filter könnte man gut realisieren, wenn die Filter selber streams sind und ein Filter auf dem anderen arbeitet.

    Da sehe ich ehrlich gesagt jetzt keinen Vorteil. Warum sollen die Filter Streams sein? Sie beeinflussen doch nur das Verhalten eines Streams, stellen selbst aber keinen Stream dar.



  • Naja, ich sehe es als eine Art FilterStream.
    Ein Stream ist doch konzeptionell nichts anderes als ein Bytestrom. Es ist völlig egal, woher die Bytes kommen, die ich z.B. in einem ZipStream auslese, der ZipStream liest sie einfach, komprimiert ein bisschen was und das was rauskommt ist wieder ein Bytestrom (was auch sonst?), der hoffentlich kürzer ist.
    Abstrakter als ein ZipStream ist IMHO ein allgemeiner FilterStream. Ich könnte mir vorstellen, von istream FilterInputStream abzuleiten (welcher einen anderen Stream liest und verändert wiedergibt) und davon
    - ZipInputStream
    - ExtensionFilterInputStream
    - TempFilterInputStream
    - usw.

    Du musst mir da nicht zustimmen, aber für mich sind Filter, die einen Stream lesen und praktisch einen veränderten Stream weiterleiten eben auch Streams, bzw. finde ich es eine gute Idee, sie zu streams zu machen. Dadurch benutzt man sie wie nen normalen Stream und interessiert sich gar nicht dafür, was er überhaupt macht.
    Und einfach zu benutzen ist es auch:

    ...
    TempFilterInputStream myTempFilterStream(myDirStream);
    ExtensionFilterInputStream myFilteredStream(".cpp", myTempFilterStream);
    
    myFilteredStream  >>  ... ;
    


  • sie geben keine werte weiter, und verändern auch keine. sie testen nur 🙂



  • Achso? Ich hab das so verstanden, dass ein Stream von Files reinkommt und die Filter alles unpassende aus dem Stream herausnehmen.
    Na dann...



  • Ein Stream ist doch konzeptionell nichts anderes als ein Bytestrom

    Meine Filter sind aber keine Byteströme. Die Filter sind vielmehr wie ein
    Türsteher der entscheidet ob jemand in den Strom überhaupt erst reinkommt.

    Deine Alternative, so wie ich es verstehe, würde bedeuten, dass ich erst einen allgemeinen Verzeichnisstrom baue, der alle Einträge auswählt und dahinter dann mehrere Filterströme setze, die nur noch bestimmte Einträge durchlassen. Diesen Ansatz habe ich aber bewusst verworfen, da er mir zu ineffizient ist. Warum soll ich erst alle Unterverzeichnisse öffnen und durchsuchen (was für jeden Eintrag ein Systemaufruf ist) nur um dann später rauszufinden, dass der Nutzer nur Unterverzeichnisse mit dem Namen "src" durchsuchen will?

    Du musst mir da nicht zustimmen, aber für mich sind Filter, die einen Stream lesen und praktisch einen veränderten Stream weiterleiten eben auch Streams

    Das sehe ich ein, ist aber nicht meine Situation. Wie gesagt, in meiner Situation stehen die Filter logisch gesehen *vor* dem Strom. Zuerst kommen die Systemaufrufe, dann die Filter und alles was diese durchlassen wird dem Nutzer als Strom dargestellt.

    Ich bin allerdings mittlerweile nicht mehr davon überzeugt, dass dieses Stromkonzept in diesem Fall überhaupt einen Sinn macht. Ein Input-Iterator wäre imo ausreichend (ich habe derzeit beides, einen Strom und einen Iterator der auf einem Strom arbeitet).

    Optimizer schrieb:

    Und einfach zu benutzen ist es auch:

    ...
    TempFilterInputStream myTempFilterStream(myDirStream);
    ExtensionFilterInputStream myFilteredStream(".cpp", myTempFilterStream);
    
    myFilteredStream  >>  ... ;
    

    Den Punkt verstehe ich nicht.
    Was ist hieran weniger einfach?

    Filter noTempFiles;
    Filter cppFilesNotTempFiles("*.cpp", noTempFiles);
    dirstream str(".", cppFilesNotTempFiles);
    for (string s; str >> s;)
    {
    ...
    }
    

    Meine and/or/not-Combiner erlauben jetzt folgendes:

    // zeigt alle cpp-Dateien des aktuellen Verzeichnisses
    // die *nicht* am 17.10.2004 verändert wurden
    dirstream str(".", 
    		make_filter(
    			and_f(new PatternFilter("*.cpp"), 
    				not_f(LastModifiedOn(17, 10, 2004)
    				)
    			)
    		)
    );
    copy(dirstream_iterator(str), dirstream_iterator(), ostream_iterator<string>(cout, "\n"));
    

    Man kann also sowohl IFilter-Objekte (per new alloziert) als auch Prädikate/Funktionen/Memberfunktionen kombinieren. Das Ergebnis ist ein Combiner-Objekt das durch make_filter in ein IFilter-Objekt adaptiert wird.
    Das Combiner-Objekt übernimmt dabei die Zerstörung der Teilobjekt und wird selbst durch den Strom zerstört.

    Das ist natürlich alles noch reichlich unflexibel. Aber solange ich nicht weiß, wie man das Ganze konkret einsetzen kann, werde ich wohl erstmal damit leben können.



  • lass doch make_filter raus, und lass and_f() bzw not_f ein IFilter* sein



  • Das sehe ich ein, ist aber nicht meine Situation. Wie gesagt, in meiner Situation stehen die Filter logisch gesehen *vor* dem Strom. Zuerst kommen die Systemaufrufe, dann die Filter und alles was diese durchlassen wird dem Nutzer als Strom dargestellt.

    Ok, ist klar jetzt. Ist hierfür sicherlich auch der sinnvollere Weg, den du gewählt hast.


Anmelden zum Antworten