ein Wrapper?



  • Ja, sowas. Datei ist Fehlerhaft, also beschwert sich die Klasse (exception ist der Weg, der zu gehen ist, meiner Meinung nach). Der Aufrufer kann dann zum Beispiel nach einem korrigierten Namen fragen.
    Man könnte sich dann auch überlegen, ob man zwischen 'Datei existiert nicht' und 'Datei liegt nicht in einem abspielbaren Format vor' unterscheiden möchte.



  • Bis ich etwas besseres habe, nur für den Übergang um den Schnitzer ungültiges Objekt, wäre das ok?

    class Sound
    {
    public:
    
        Sound( const std::string& sound_name ): name( sound_name )
        {
            if ( !getSoundFile() )
            {
                //Korrektur oder
                //Ausnamhme werfen?
            }
        }
    
        void play() const
        {
            PlaySound( file_name.c_str(), NULL, SND_ASYNC );
        }
    
        static void stop()
        {
            PlaySound( NULL, 0, 0 );
        }
    
    private:
    
        std::string name;
        std::string file_name;
    
        bool getSoundFile()
        {
            const std::string suffix = ".wav";
            const std::string filename = Files::soundsFolder() + name + suffix;
            std::ifstream file( filename );
            if ( !file )
            {
                std::cerr << "Sound::getSoundFile(): file error\n";
                std::cerr << "file: " << filename;
                _pressKey();
                return false;
            }
            file_name = filename;
            return true;
        }
    };
    

    Und wenn ich jetzt eine Korrektur oder einen Vorschlag machen möchte, wie komme ich wieder zu der Stelle, wo dieses Objekt erzeugt werden sollte? Also wieder vor den Konstruktor Aufruf?



  • Der Aufrufer muss sich halt überlegen, was er macht, wenn eine Exception geworfen wird. Also, ob er z.B. dann nochmal eine Abfrage nach dem Dateinamen macht.

    Als kleine Anregung eine Programm, dass auf die Eingabe 42 wartet:

    #include <iostream>
    #include <string>
    
    void input42()
    {
        std::cout << "input number: ";
        std::string input;
        std::cin >> input;
    
        if(input != "42" )
            throw("Not 42");    
        else
            std::cout << "\n congrats, input correct";
    }
    
    bool input()
    {
        try
        {
            input42();
            return true;
        }catch(...)
        {
            return false;
        }
    }
            
    
    int main()
    {
      while(!input());
    }
    


  • Ich meinte eher,

    //code
    Sound s( "test" );
    //code
    s.play();
    //code
    

    Wenn "test" falsch ist, korrigiert wird, wie komme ich wieder zu

    //code
    Sound s( "test" );
    //code
    s.play();
    //code
    

    ?
    Ich könnte nach der Korrektur aus if ( !getSoundFile() ) springen und weitermachen, aber wenn auch die Korrektur falsch ist?



  • @lemon03

    z.B.

    for(;;)
    {
      try
      {
        Sound s( "test_der_sich_woher_auch_immer_aendert" );
        s.play();
        break;
      }
      catch(...)
      {
      }
    }
    

    Aber wäre nicht irgendwas wie 'setSoundFile' hier günstiger?



  • Bitte ohne Endlosschleife und ohne try/catch. Ausnahme werfen ist kein Problem. Deshalb das oder im obigen Kommentar.

    Geht drum, wenn ich keine Ausnahme werfen möchte, sondern zur Laufzeit korrigieren will, wie mache ich das? Ich kann ja schlecht einen Konstruktor rekursiv aufrufen?

    Sound s( "test_der_sich_woher_auch_immer_aendert" );
    

    Das kann passieren, wenn ich oder ein potentieller jemand im Code beim programmieren den Namen des Sound falsch schreibt. Oder vergessen hat, die Datei in den richtigen Ordner zu kopieren etc.



  • Wie willst du zur Laufzeit korrigieren, was du zur 'Entwicklungszeit' falsch geschrieben hast?



  • Ich dachte, ich greife damit einen Vorschlag auf? Darum geht es doch? Nicht den Code korrigieren und neu kompilieren, sondern zur Laufzeit ändern.

    @schlangenmensch sagte in ein Wrapper?:

    Außerdem soll ja irgendwann vlt. auch mal jemand anderes deinen Code verwenden können. Bei einer Fehlerhaften Eingabe will ich als Entwickler ganz bestimmt keine Ausgabe, die mir sagt, dass ich jetzt das Programm beenden muss, sondern eben die Möglichkeit damit umzugehen, wie ich es für richtig halte.

    Mein Plan wäre dann, den Konstruktor mit dem korrigierten string neu aufzurufen. Das geht dann wahrscheinlich nicht. Also bleibe ich beim Aussteigen, nur das jetzt kein Objekt mehr erzeugt wird.



  • Du willst normalerweise keinen Dateinamen fest in den Source Code einprogrammieren.
    Zur Laufzeit kannst du z.B. Eingaben von einem Anwender auf Korrektheit überprüfen.

    Es ging mir dabei darum, dass die Klasse für verschiedene Anwendungsfälle verwenden kannst. Darum die Idee, die Klasse nur eine Mitteilung raus geben zu lassen, dass ein Fehler aufgetreten ist. Wie mit dem Fehler umgegangen wird, ist dann in der Verantwortung vom Aufrufer. Das heißt, je nach Anwendung kannst du das Programm beenden, oder irgendwas anderes machen. Das soll aber nicht die "Sound Abspiel Klasse" entscheiden.



  • Danke. Wenn ich ein File lesen will, eine Tabelle, einen Spielstand, irgendwas, was nicht im Code festgehalten ist, wie muss ich das denn sonst machen, außer den Dateinamen anzugeben? Es ist ja auch nicht der Dateiname selbst, der wird erzeugt, aber es muss doch einen Namen geben, damit ich und der Code weiß, was gelesen werden muss?

    Bezüglich der Fehlerbehandlung kam mir eine Struktur, vielleicht eine Klasse oder ein namespace in den Sinn. Dort wird anhand der Parameter, die man übergibt, entschieden, wie reagiert wird. Wäre das praktikabel?

    Vielleicht sollte ich kurz erläutern, worin meine Anwendung besteht. Es ist kein Programm, was abläuft, der Anwender gibt ein bisschen was ein und guckt dann zu.
    Es werden Funktionen und Möglichkeiten angeboten, selbst etwas zu programmieren. Der ausführbare Code muss erst vom Anwender geschrieben werden. Und meine Fehlermeldungen sind eher wie Syntaxfehler zu verstehen. Dies oder jenes klappt so nicht und muss korrigiert werden.



  • @lemon03 sagte in ein Wrapper?:

    Danke. Wenn ich ein File lesen will, eine Tabelle, einen Spielstand, irgendwas, was nicht im Code festgehalten ist, wie muss ich das denn sonst machen, außer den Dateinamen anzugeben? Es ist ja auch nicht der Dateiname selbst, der wird erzeugt, aber es muss doch einen Namen geben, damit ich und der Code weiß, was gelesen werden muss?

    Ja, aber in aller Regel wird der Name durch den Programmablauf bestimmt. Bei einem Spielstand entscheidet der Spieler, welchen er laden möchte. Bei einer zu bearbeitenden Textdatei, bestimmt auch der Anwender, welche er öffnen will. Wenn es ein Sound zum Abspielen bei einer bestimmten Aktion ist, könnte ich mir vorstellen, dass der aus einer Config Datei kommt, damit man den, ohne neu zu kompilieren, ändern kann.

    Bezüglich der Fehlerbehandlung kam mir eine Struktur, vielleicht eine Klasse oder ein namespace in den Sinn. Dort wird anhand der Parameter, die man übergibt, entschieden, wie reagiert wird. Wäre das praktikabel?

    Das kann unter umständen Sinnvoll sein. Aber die übergeordnete Klasse muss erstmal entscheiden, was sie macht, wenn ein Fehler auftritt. Zum Beispiel könnte sie eine Klasse aufrufen, die den Fehler irgendwo logt o.ä.

    Vielleicht sollte ich kurz erläutern, worin meine Anwendung besteht. Es ist kein Programm, was abläuft, der Anwender gibt ein bisschen was ein und guckt dann zu.
    Es werden Funktionen und Möglichkeiten angeboten, selbst etwas zu programmieren. Der ausführbare Code muss erst vom Anwender geschrieben werden. Und meine Fehlermeldungen sind eher wie Syntaxfehler zu verstehen. Dies oder jenes klappt so nicht und muss korrigiert werden.

    Also wird das eine Art Skript Interpreter? Aber grade der soll sich ja nicht beenden, wenn ein Fehler auftritt, sondern dem Anwender die Möglichkeit bieten, seinen Code zu korrigieren, oder eventuell noch weitere Fehler finden? Aber wie genau das Verhalten im Fehlerfall sein soll, ist im endeffekt deine Design Entscheidung.



  • @schlangenmensch sagte in ein Wrapper?:

    Ja, sowas. Datei ist Fehlerhaft, also beschwert sich die Klasse (exception ist der Weg, der zu gehen ist, meiner Meinung nach). Der Aufrufer kann dann zum Beispiel nach einem korrigierten Namen fragen.
    Man könnte sich dann auch überlegen, ob man zwischen 'Datei existiert nicht' und 'Datei liegt nicht in einem abspielbaren Format vor' unterscheiden möchte.

    Wäre für mich kein klassischer Fall von Exceptions. Fände ich persönlich übertrieben.

    Nicht mal die STL selber wirft da ne Exception.
    Wenn eine Datei nicht existiert ist das aus meiner Sicht ein übliches und nicht seltenes Verhalten. Also eher keine Ausnahme.

    std::ifstream MySoundFile( "./nichtexistierendeDatei.mp3", std::ios::binary );
    

    wirft auch keine Exception, sondern man kann mit diversen Funktionen nachprüfen ob der Datenstrom lesbar/schreibbar ist.

    In dem Fall des SoundFiles würde ich dann das tatsächliche Öffnen der Datei im Konstruktor auch unterlassen und stattdessen das Prüfen/Öffnen der Datei spätestens beim "play"-Aufruf machen und im Fehlerfall einen bool oder einen Errorcode zurückliefern und zusätzlich eine Funktion "isValidFile" oder ähnliches anbieten, falls der Nutzer wünscht, die Gültigkeit der Datei vor dem "play"-Aufruf zu prüfen.



  • @schlangenmensch sagte in ein Wrapper?:

    Also wird das eine Art Skript Interpreter?

    Eher eine, wie man früher bei Interpretersprachen sagte, "Befehlserweiterung". Etwas wie eine Bibliothek, nur auf einem sehr niedrigeren Niveau. Und nur für eine bestimmte Oberfläche und einen bestimmten Anwendungszweck.



  • Vielleicht sollte ich mal nachholen, ein Beispiel für die Anwendung zu zeigen. Nur um eine Vorstellung zu haben.

    Dies war eine kleine Fingerübung für den bisherigen Code.
    towers of hanoi in windows console



  • @lemon03 Ich sehe folgende Möglichkeiten:

    • Verbesserungsabfrage im ctor (NICHT zu empfehlen)
    • Funktion Sound::is_ready oder so hinzufügen und die Abfrage in einer Schleife bei der Erstellung übernehmen (vermutlich mein Weg)
    • exception abfangen und Abfrage starten

    Vielleicht habe ich aber noch eine Möglichkeit übersehen.



  • @ sagte in ein Wrapper?:

    I/O im Konstruktor ist sehr unschön, meiner Meinung nach. 🤠

    Wieso?
    Bzw. genauer: An welchem Teil störst du dich?

    Ich sehe da genau in (vermutliches) Problem, und das ist der Aufruf der _pressKey() Funktion. Checken ob ein File existiert im Ctor ist OK. Logging im Ctor ist OK. Im Fehlerfall (und nur im Fehlerfall) darauf zu warten dass der Benutzer ne Taste drückt (was _pressKey() wohl vermutlich macht), ist mMn. nicht OK.

    Der Grund dafür hat aber nicht viel mit "I/O im Konstruktor" zu tun.



  • Meine "Sound Klasse" wurde ja schon gründlich auseinandergenommen, weshalb man davon ausgehen kann, das sie so nicht mehr existiert. Die ganze Entwicklung wurde für sie sogar pausiert, bis andere Fragen geklärt sind.

    Aber ich wollte nur kurz etwas zu _presskey() schreiben. Vielleicht wird da zuviel hinein interpretiert. Es soll nur mich eine Anzeige sein, das etwas nicht geklappt hat. Da ich eh einige Funktionen habe, die auf Tastendruck warten, habe ich eine solche dort einfach eingefügt. Irgendwie muss es ja stoppen, damit ich die Ausgabe sehen kann.

    Das dies keine optimale Idee ist, ok. Aber es war kein tieferer Sinn dahinter. Nur eben Ausgabe anzeigen.



  • Wenn ich die Funktion, die einfach ein "ready" ausgibt und wartet, also ready();eingefügt hätte, wären dann ebensolche Fragen aufgetaucht, obwohl die Funktion die selbe ist? Oder wäre man dann der Ansicht, danach würde das Programm beendet?



  • @lemon03 sagte in ein Wrapper?:

    Wenn ich die Funktion, die einfach ein "ready" ausgibt und wartet, also ready();eingefügt hätte, wären dann ebensolche Fragen aufgetaucht, obwohl die Funktion die selbe ist? Oder wäre man dann der Ansicht, danach würde das Programm beendet?

    Bin mir nicht sicher ob ich die Frage verstehe, bzw. ob du verstanden hast warum ich das Warten auf User-Input in einem solchen Konstruktor doof finde.

    Mir geht es darum dass so eine Klasse schwer korrekt zu verwenden ist bzw. auch das "principle of least astonishment" verletzt. Stell dir vor du kopierst die Klasse in ein Spiel, kein Textmode Spiel sondern eins mit schöner Grafik, das Fullscreen läuft, und dann überleg dir was passiert wenn er ein File nicht findet.

    Wenn du nur die Fehlermeldung ausgeben möchtest, dann gib halt einfach die Fehlermeldung aus. Wenn du unbedingt möchtest dass man sie bestätigen muss, dann mach wenigstens ne eigene Funktion showModalErrorMessage oder so, die die Meldung anzeigt und auf die Bestätigung wartet. Dann kann man diese wenigstens so anpassen wie man es braucht, je nach Projekt.

    Das selbe gilt im Prinzip auch für die Ausgaben auf std::cerr. D.h. eine spezielle Logging-Funktion wäre besser als einfach auf std::cerr rausschreiben.


    Der wichtigste Punkt ist IMO aber wirklich dass hier das "principle of least astonishment" dadurch verletzt wird, dass ein Konstruktor einer Sound-Klasse eine Meldung anzeigt und dann auf User-Input wartet. Und damit das Programm anhält und nebenbei noch den Zustand von std::cin ändert. Damit rechnet man einfach nicht.



  • So wie ich das verstanden habe, möchtest du dem Programmbenutzer das auswählen einer Sound-Datei überlassen? Das gehört auf keinen Fall in den Konstruktor. Bevor du überhaupt eine Instanz deines Musik-Objekts erzeugst, fragst du nach dem Dateinamen. Dann überprüfst du, ob die Datei überhaupt existiert. Tut sie das, erstelle das Musik-Objekt. Tut sie das nicht (oder fehlen dir die leserechte), frag am besten nochmal nach der Datei. In deiner Klasse solltest du bei Fehler schlussendlich nur noch Exceptions werfen. Wenn du diese dann auch behandeln willst, dann kannst du das machen.


Anmelden zum Antworten