Vortrag über Ideen für eine neue Sprache für Spieleentwickler [youtube]



  • volkard schrieb:

    resize, clear, überschreiben.

    Ich meine aus Sicht des Users. Ich will das Objekt aus dem vector wieder zurueck.



  • audacia schrieb:

    Bei "Fälle von Exceptions" geht es dir aber doch um Anwendungsszenarios, oder?

    Korrekt.

    audacia schrieb:

    Ansonsten verstehe ich nicht, wo du da in der Semantik eine Unterscheidung vornehmen willst. Kannst du das mal ausführen?

    Das versuche ich gerade herauszufinden. Wo ist die Linie, wo Exceptions falsch sind? Ich kann schwer komplett gegen Exceptions argumentieren, wenn ich sie selbst in manchen Faellen verwendet habe. Die Frage ist, was unterscheidet meine Faelle von denen anderer Leute? Gibt es Regeln, denen man folgen kann, um ein Programm zu schreiben, das Exceptions 100%-ig richtig verwendet? Kann man ein Feature bauen, das genau auf diese Faelle zugeschnitten ist?



  • audacia schrieb:

    volkard schrieb:

    v[0] gäbe eine normale referenz zurück. also kann man das nicht wegmoven.

    Und was ist dann hier:

    Movable u;
    Movable v[10];
    move (u); // u ist fortan unberührbar
    move (v[rand () % 10]); // ...und v?
    

    Gerade sowas brauche ich ja, wenn ich meinen eigenen Vector implementiere.

    Ich denke eher an

    vector<ifstream> streams;
    streams.emplace_back("c:/autoexec.bat");
    move(streams[0]);//geht nicht
    
    vector<movable<ifstream>> streams;
    streams.emplace_back("c:/autoexec.bat");
    move(streams[0]);//geht
    streams[0]->close();//absturz, selber schuld
    
    ifstream in("c:/autoexec.bat");
    move(in);//geht
    //in ist jetzt weg. 
    in.close();//compilerfehler
    


  • Kellerautomat schrieb:

    volkard schrieb:

    resize, clear, überschreiben.

    Ich meine aus Sicht des Users. Ich will das Objekt aus dem vector wieder zurueck.

    Anschauen kannste es jederzeit. Rausmoven? Hmm, könnte mir sogar Algos vorstellen, wo man das machen will.
    Zur Not damit keine toten Objekte im vector verbleiben müssen eine Methode.

    ifstream meiner=v.erase_move(rand()%10);
    

    Und wer die Gefahr liebt

    ifstream meiner=move(move_cast(v[0]));
    //bald kackt hier was abm außer ich repriere es schnell. 
    new(&v[0]) ifstream("/dev/urandom");
    


  • Kellerautomat schrieb:

    Guck dir mal an, wie sowas in Haskell gemacht wird. Das ist elegant. Stichwort Maybe/Either.

    Maybe ist kein Ersatz für Exceptions, weil man damit nicht kommunizieren kann was eigentlich schief gegangen ist.
    Wählst du absichtlich Beispiele die vermutlich 90% der Forenuser erst googeln müssen?
    Maybe=boost::optional
    Either=boost::variant oder ein beliebiger anderer Discriminated Variant Container.
    Somit hätten wir den Teil "wie sowas in Haskell gemacht wird" schonmal abgehakt.
    Dass speziell der Umgang mit Discriminated Variants in C++ vermutlich viel aufwendiger zu schreiben (und auch zu lesen) ist, ist wieder eine andere Sache. Geben tut es diese Mittel auf jeden Fall.
    Und ich weiss auch nicht was das mit dem Thema Exceptions zu tun haben soll.
    Exceptions gibt es in Haskell genau so.
    Hier
    http://www.haskell.org/haskellwiki/Exception
    steht sogar schön beschrieben wann man Exception verwenden sollte.

    An exception denotes an unpredictable situation at runtime, like "out of disk storage", "read protected file", "user removed disk while reading", "syntax error in user input". These are situation which occur relatively seldom and thus their immediate handling would clutter the code which should describe the regular processing. Since exceptions must be expected at runtime there are also mechanisms for (selectively) handling them.

    Lässt sich mMn. 1:1 auf C++ übertragen.

    Kellerautomat schrieb:

    Zu deinem std::string Beispiel sag ich einfach mal nix, da du offensichtlich meine Posts garnicht richtig gelesen hast. Du findest die Antwort dort.

    Nein, finde ich nicht. Zeig sie mir.

    Kellerautomat schrieb:

    Exceptions verwenden kann schonmal Sinn machen - WENN man einen guten Grund dafuer hat. Aber Exceptions einfach so als universelle Loesung abzutun ist einfach nur Unsinn.

    Ach, jetzt auf einmal?
    BTW: der gute Grund dafür Exceptions zu verwenden steht in dem von mir gerade von haskellwiki zitierten Abschnitt beschrieben.

    Kellerautomat schrieb:

    hustbaer schrieb:

    ps:

    Kellerautomat schrieb:

    Es gibt so ziemlich keinen Fall, in dem ich eine Exception verwenden wuerde. (...) File konnte nicht geoeffnet werden? Das ist doch vollkommen erwartet.

    "ist doch vollkommen erwartet" ist aber leider kein Argument.
    Natürlich ist es erwartet. Aber warum sollte man deswegen keine Exceptions verwenden wollen/sollen/dürfen?
    So lange du das nicht erklären kannst het die Feststellung "ist doch vollkommen erwartet" überhaupt keinen Bezug zum Thema.

    Uebersetz mal "Exception" auf Deutsch.

    Ich hab fast damit gerechnet dass das jetzt kommt. Hab aber gehofft dass du intelligenter bist. Das ist nämlich wieder kein Argument. Wie das Feature heisst ist erstmal total uninteressant. Interessant ist was man damit machen kann.

    Wobei es bei "File konnte nicht geoeffnet werden" genau genommen zwei Fälle zu unterscheiden gibt:

    1. Es ist zu erwarten dass das File existiert, und wenn es nicht existiert (oder aus anderen Gründen nicht geöffnet werden kann), dann ist es ein "unerwartetes Ereignis" das als solches behandelt werden sollte.
      Den Fall hast du z.B. wenn du Asset Files in einem Spiel lädst, ein File aufmachen willst das der User in einem Open-File Dialog ausgewählt, ein Logfile mit "open or create" aufmachen willst etc.
    2. Man weiss nicht so genau ob das File existiert bzw. ob man es aufmachen kann.
      Den Fall hast du z.B. wenn du ein Config-File aufmachen willst welches aber optional ist. Gibt sicherlich noch etliche andere "Standardfälle", aber mir fällt jetzt auf die Schnelle kein weiterer ein.

    Und auf (1) trifft nun genau das zu was haskellwiki über Exceptions schreibt: die Fälle kommen nicht oft vor, und man will sich seinen Code nicht damit zumüllen überall entsprechende Checks reinzuschreiben.
    Und, nur weil es gerade zufällig passt: die (1) Fälle kann man auch durchaus als "Ausnahmen" bezeichnen.

    Was (2) angeht: dafür macht man in C++ idealerweise das selbe wie in Haskell, nämlich TryXxx Funktionen. Also z.B. optional<File> TryOpenFile(string path, IOError& out_error) o.Ä. Idealerweise mit einem Overload ohne den Output-Parameter, denn der aufgetretene Fehler interessiert oft gar nicht.
    Du scheinst (1) aber komplett zu ignorieren, und beschränkst dich auf die wesentlich selteneren Fälle (2).

    Kellerautomat schrieb:

    hustbaer schrieb:

    Kellerautomat schrieb:

    shared_ptr: Hab ich persoenlich noch nie gebraucht, kann also nicht nachvollziehen was das bringen soll. Und natuerlich kann man mit RAII use-after-free Fehler bauen. Das verhindert man am besten, indem man ein API ordentlich designed.

    Uiiiiiii. Wieder kein Argument. Diesmal nicht weil der Zusammenhang fehlt, sondern weil es einfach nur eine Behauptung ist. Der ich widerspreche: geht halt nicht immer. Und je nachdem was man so programmiert kann "nicht immer" sehr oft sein.

    Zeig mir die Faelle, wo du glaubst shared_ptr zu brauchen. Ich zeig dir, warum du ihn nicht brauchst.

    OMG, du bist echt stur. Oder unerfahren. Oder beides.

    Ein Programm kommuniziert über diverse Transport Channels (RS-232, TCP/IP Verbindung, HTTP Verbindung, ...) mit diversen Endpoints (Web-Services, Periphäriegeräte die an einem gemeinsamen Bus hängen oder verschiedenen Funktionsblöcken in diesen Geräten etc.).
    Wichtig ist nur: es gibt mehrere Endpoints die über den selben Channel angesprochen werden müssen.
    Gibt genügend reale Beispiele dafür, musst du mir im Zweifelsfall einfach glauben.
    Und da teilen sich alle Objekte die eine Verbindung zu einem Endpoint darstellen halt einfach die Ownership des Channel Objekts.

    Thread-safe Signals.
    Wenn dir das Problem nicht bekannt ist, dann lies dir die Doku zu Boost.Signals2 durch, da wird es ausführlich erklärt:
    http://www.boost.org/doc/libs/1_56_0/doc/html/signals2/thread-safety.html#idp431602368
    Der Knackpunkt ist: Das Signal muss sicherstellen dass der Slot nicht gelöscht wird während er gerade aufgerufen wird. Das Signal muss also während des Aufrufs "owner" des Slots sein. Gleichzeitig muss aber natürlich der der den Slot connected hat auch "Owner" des Slots sein, da der Slot ja sonst sofort wieder zerstört ("dicsonnected") würde.

    Billig kopierbare immutable Objekte mit Hilfe von shared_ptr<T const> bauen.
    Ist ne "Optimierung", aber eine sehr gute. Ist einfach, effizient und sicher.

    ...

    Kellerautomat schrieb:

    Und bevor ichs vergesse, was mich an Exceptions besonders stoert, ist, dass Interfaces einem nicht verraten, ob und welche Exceptions sie werfen koennen. Wenn ich mal eine Exception vergesse zu behandeln, die dann bei mir nie auftritt, hab ich ein Problem. Vielleicht sind Javas checked Exceptions doch nicht so schlecht.

    Na geht ja. DAS ist nämlich wirklich ein Argument. Wenn auch mMn. kein besonders gutes. Die Aufweichung von "kann Exceptions der folgenden Typen werfen" zu einfach nur "kann Exceptions werfen" bzw. "kann keine Exceptions werfen" ist nämlich in der Praxis sehr sehr ... praktisch.
    Weil es praktisch kaum managebar ist sämtliche Exception-Typen aufzulisten die etwas werfen kann. Ob mit oder ohne Sprachsupport spielt dabei kaum eine Rolle.

    Was das "Exception vergessen zu behandeln" angeht: hier nähern wir uns schön langsam dem eigentlichen Problem: nämlich dass die Klassenhierarchien der Exception von Java, C++ und C# total beknackt sind, und C++ es obendrein noch erlaubt beliebige Klassen zu werfen.
    Das sehe ich auch als Problem. Es führt aber nicht die ganze Exception-Sache ad Absurdum, sondern macht lediglich den Umgang mit Exceptions in C++ etwas lästig.



  • Zumal heute nur noch C++ eingesetzt wird, wenn es gar nicht anders geht. Wer die Wahl hat, der meidet C++ wie der Teufel das Weihwasser. Was meint ihr, wieviel von 100% Software-Entwickler haben mit C++ zu tun? Ich denke es sind nicht mehr wie 5% oder so. Die meisten arbeiten doch mit Webgeschichten oder wenn es Desktop ist mit C#, Obj-C oder Java falls es Mobil ist.

    Freiwillig nimmt sich doch niemand C++ als erste Wahl, wer schiesst sich und seine Kunden denn gerne schnell freiwillig ins Bein?



  • Da muss man sich nur mal die Tiobe Index anschauen, der sagt zwar nicht viel aus, aber eine Sprache ueber die nicht oft gesprochen wird, wird auch in der Anwendung nicht mehr die grosse Rolle spielen. Immer mit einem Allzweckwerkzeug zu arbeiten ist auch wenig professionell und sehr fehleranfaellig. Was aktuell genutzt wird, kann man auch gut in den Artikeln in den Fachzeitschriften sehen, oder auch in Stellenanzeigen.

    http://www.tiobe.com/index.php/content/paperinfo/tpci/index.html



  • CppFewUsers schrieb:

    Zumal heute nur noch C++ eingesetzt wird, wenn es gar nicht anders geht. Wer die Wahl hat, der meidet C++ wie der Teufel das Weihwasser. Was meint ihr, wieviel von 100% Software-Entwickler haben mit C++ zu tun? Ich denke es sind nicht mehr wie 5% oder so. Die meisten arbeiten doch mit Webgeschichten oder wenn es Desktop ist mit C#, Obj-C oder Java falls es Mobil ist.

    Freiwillig nimmt sich doch niemand C++ als erste Wahl, wer schiesst sich und seine Kunden denn gerne schnell freiwillig ins Bein?

    C# fällt weg, weil Mono unter aller Sau buggy ist. Visual Studio ist einfach lächerlich zurückgeblieben.
    Apple-Müllsprachen fallen natürlich auch weg.
    Java ist scheiße und tot. Es gibt keine vernünftige Entwicklungsumgebung. Soll ich ernsthaft eine Programmiersprache einsetzen, die standardmäßig Malware ("Toolbars") mitliefert?
    Ich benutze C++, weil es im Gegensatz zu anderen Sprachen funktioniert. Es gibt zwei brauchbare Compiler (GCC, Clang), eine brauchbare IDE (QtCreator) und kann zur Not ohne IDE sinnvoll benutzt werden.

    Ich experimentiere zurzeit damit weniger Exceptions einzusetzen. Dafür habe ich ein Template error_or<typename Value> , das man für Rückgabetypen benutzen kann. Es ist dafür gedacht throw system_error(..) zu ersetzen, aber keine anderen Typen von Ausnahmen. bad_alloc halte ich für sinnvoll, weil das sehr selten ist und kaum sinnvoll behandelt werden kann.
    Ausnahmen, die nicht bad_alloc oder system_error sind, halte ich in der Regel für unnötig.

    error_or<file_handle> open_read(path const &name)
    {
        int fd = open(name.c_str(), O_RDONLY);
        if (fd < 0)
        {
            return error_code(errno, system_category());
        }
        return file_handle(fd);
    }
    
    int main()
    {
        //je nach Situation benutzt man das entweder so
        error_or<file_handle> opened = open_read("name");
        if (opened.is_error())
        {
            cerr << *opened.error() << '\n';
            return 1;
        }
        //oder so
        file_handle opened2 = open_read("name").get(); //wirft system_error, wenn Fehler
    }
    


  • hustbaer schrieb:

    Either=boost::variant oder ein beliebiger anderer Discriminated Variant Container.
    Somit hätten wir den Teil "wie sowas in Haskell gemacht wird" schonmal abgehakt.

    Bei Either in Haskell kann man aber über die Monad-Instanz und mittels do-Notation Code schreiben, bei dem der Fehler (also ein Left -Wert) automatisch propagiert wird, ähnlich wie bei Exceptions. Allerdings lokal in einem monadischen Block und nicht unerwartet über komplett unzusammenhängende Programmteile hinweg. So etwas geht mit boost::variant o.ä. nicht, und selbst wenn man die monadischen Operationen anbieten würde, wäre die Syntax so umständlich, dass es niemand nehmen würde. Damit vereinigt Either die Vorteile von Fehlerbehandlung über Rückgabewerte (einfache Struktur, typsicher, weniger Bugs) und Exception (keine manuellen Checks nach jedem Funktionsaufruf nötig).

    Übrigens ist das auch das, was auf der HaskellWiki-Seite beschrieben wird. Der dortige Exceptional -Typ ist isomorph zu Either und ExceptionalT ist analog zu ExceptT , dem Monad-Transformer von Either .

    Exceptions im klassischen Sinne gibt es in Haskell übrigens auch, allerdings nur in IO -Code und manche halten ihre Existenz auch für einen Fehler. Wenn möglich, sollte Either / ExceptT bevorzugt werden.



  • hustbaer schrieb:

    Maybe ist kein Ersatz für Exceptions, weil man damit nicht kommunizieren kann was eigentlich schief gegangen ist.
    Wählst du absichtlich Beispiele die vermutlich 90% der Forenuser erst googeln müssen?
    Maybe=boost::optional
    Either=boost::variant oder ein beliebiger anderer Discriminated Variant Container.
    Somit hätten wir den Teil "wie sowas in Haskell gemacht wird" schonmal abgehakt.
    Dass speziell der Umgang mit Discriminated Variants in C++ vermutlich viel aufwendiger zu schreiben (und auch zu lesen) ist, ist wieder eine andere Sache. Geben tut es diese Mittel auf jeden Fall.
    Und ich weiss auch nicht was das mit dem Thema Exceptions zu tun haben soll.
    Exceptions gibt es in Haskell genau so.
    Hier
    http://www.haskell.org/haskellwiki/Exception
    steht sogar schön beschrieben wann man Exception verwenden sollte.

    An exception denotes an unpredictable situation at runtime, like "out of disk storage", "read protected file", "user removed disk while reading", "syntax error in user input". These are situation which occur relatively seldom and thus their immediate handling would clutter the code which should describe the regular processing. Since exceptions must be expected at runtime there are also mechanisms for (selectively) handling them.

    Lässt sich mMn. 1:1 auf C++ übertragen.

    Maybe/Either sind nicht "nur" optional/variant. Sieh dir an, wie man damit arbeitet. Und zwar nicht nur am Ende (da wo try/catch stehen wuerde), sondern wie Errors weitergegeben werden. Das sind im Prinzip Error-Codes, nur besser.

    Aus dem Code der von dir verlinkten Seite:

    main :: IO ()
    main =
       do result <- runExceptionalT (readText "test")
          case result of
             Exception e -> putStrLn ("When reading file 'test' we encountered exception " ++ show e)
             Success x -> putStrLn ("Content of the file 'test'\n" ++ x)
    

    Das ist auch nur ein besser benanntes Either, das eben entweder Exception e oder Success x sein kann. Steht auch hier:

    The great thing about Haskell is that it is not necessary to hard-wire the exception handling into the language. Everything is already there to implement the definition and handling of exceptions nicely.

    hustbaer schrieb:

    Kellerautomat schrieb:

    Zu deinem std::string Beispiel sag ich einfach mal nix, da du offensichtlich meine Posts garnicht richtig gelesen hast. Du findest die Antwort dort.

    Nein, finde ich nicht. Zeig sie mir.

    Eine fehlgeschlagene Memory-Allokation ist ein Fatal-Error. Also Exception.

    hustbaer schrieb:

    Kellerautomat schrieb:

    Exceptions verwenden kann schonmal Sinn machen - WENN man einen guten Grund dafuer hat. Aber Exceptions einfach so als universelle Loesung abzutun ist einfach nur Unsinn.

    Ach, jetzt auf einmal?
    BTW: der gute Grund dafür Exceptions zu verwenden steht in dem von mir gerade von haskellwiki zitierten Abschnitt beschrieben.

    Vielleicht hab ich zu kurz gedacht. Soll mal vorkommen.

    hustbaer schrieb:

    Wobei es bei "File konnte nicht geoeffnet werden" genau genommen zwei Fälle zu unterscheiden gibt:

    1. Es ist zu erwarten dass das File existiert, und wenn es nicht existiert (oder aus anderen Gründen nicht geöffnet werden kann), dann ist es ein "unerwartetes Ereignis" das als solches behandelt werden sollte.
      Den Fall hast du z.B. wenn du Asset Files in einem Spiel lädst, ein File aufmachen willst das der User in einem Open-File Dialog ausgewählt, ein Logfile mit "open or create" aufmachen willst etc.
    2. Man weiss nicht so genau ob das File existiert bzw. ob man es aufmachen kann.
      Den Fall hast du z.B. wenn du ein Config-File aufmachen willst welches aber optional ist. Gibt sicherlich noch etliche andere "Standardfälle", aber mir fällt jetzt auf die Schnelle kein weiterer ein.

    Und auf (1) trifft nun genau das zu was haskellwiki über Exceptions schreibt: die Fälle kommen nicht oft vor, und man will sich seinen Code nicht damit zumüllen überall entsprechende Checks reinzuschreiben.
    Und, nur weil es gerade zufällig passt: die (1) Fälle kann man auch durchaus als "Ausnahmen" bezeichnen.

    Was (2) angeht: dafür macht man in C++ idealerweise das selbe wie in Haskell, nämlich TryXxx Funktionen. Also z.B. optional<File> TryOpenFile(string path, IOError& out_error) o.Ä. Idealerweise mit einem Overload ohne den Output-Parameter, denn der aufgetretene Fehler interessiert oft gar nicht.
    Du scheinst (1) aber komplett zu ignorieren, und beschränkst dich auf die wesentlich selteneren Fälle (2).

    Hast du wohl Recht. Context matters.

    hustbaer schrieb:

    Kellerautomat schrieb:

    hustbaer schrieb:

    Kellerautomat schrieb:

    shared_ptr: Hab ich persoenlich noch nie gebraucht, kann also nicht nachvollziehen was das bringen soll. Und natuerlich kann man mit RAII use-after-free Fehler bauen. Das verhindert man am besten, indem man ein API ordentlich designed.

    Uiiiiiii. Wieder kein Argument. Diesmal nicht weil der Zusammenhang fehlt, sondern weil es einfach nur eine Behauptung ist. Der ich widerspreche: geht halt nicht immer. Und je nachdem was man so programmiert kann "nicht immer" sehr oft sein.

    Zeig mir die Faelle, wo du glaubst shared_ptr zu brauchen. Ich zeig dir, warum du ihn nicht brauchst.

    OMG, du bist echt stur. Oder unerfahren. Oder beides.

    Ein Programm kommuniziert über diverse Transport Channels (RS-232, TCP/IP Verbindung, HTTP Verbindung, ...) mit diversen Endpoints (Web-Services, Periphäriegeräte die an einem gemeinsamen Bus hängen oder verschiedenen Funktionsblöcken in diesen Geräten etc.).
    Wichtig ist nur: es gibt mehrere Endpoints die über den selben Channel angesprochen werden müssen.
    Gibt genügend reale Beispiele dafür, musst du mir im Zweifelsfall einfach glauben.
    Und da teilen sich alle Objekte die eine Verbindung zu einem Endpoint darstellen halt einfach die Ownership des Channel Objekts.

    Thread-safe Signals.
    Wenn dir das Problem nicht bekannt ist, dann lies dir die Doku zu Boost.Signals2 durch, da wird es ausführlich erklärt:
    http://www.boost.org/doc/libs/1_56_0/doc/html/signals2/thread-safety.html#idp431602368
    Der Knackpunkt ist: Das Signal muss sicherstellen dass der Slot nicht gelöscht wird während er gerade aufgerufen wird. Das Signal muss also während des Aufrufs "owner" des Slots sein. Gleichzeitig muss aber natürlich der der den Slot connected hat auch "Owner" des Slots sein, da der Slot ja sonst sofort wieder zerstört ("dicsonnected") würde.

    Billig kopierbare immutable Objekte mit Hilfe von shared_ptr<T const> bauen.
    Ist ne "Optimierung", aber eine sehr gute. Ist einfach, effizient und sicher.

    ...

    Ich hatte gehofft, du kommst mir jetzt nicht mit solchen kleinen Details (#2/#3) sondern mit richtigen Szenarien. Bei #1 wuerde ich einfach sicherstellen, dass der Channel laenger lebt.

    hustbaer schrieb:

    Kellerautomat schrieb:

    Und bevor ichs vergesse, was mich an Exceptions besonders stoert, ist, dass Interfaces einem nicht verraten, ob und welche Exceptions sie werfen koennen. Wenn ich mal eine Exception vergesse zu behandeln, die dann bei mir nie auftritt, hab ich ein Problem. Vielleicht sind Javas checked Exceptions doch nicht so schlecht.

    Na geht ja. DAS ist nämlich wirklich ein Argument. Wenn auch mMn. kein besonders gutes. Die Aufweichung von "kann Exceptions der folgenden Typen werfen" zu einfach nur "kann Exceptions werfen" bzw. "kann keine Exceptions werfen" ist nämlich in der Praxis sehr sehr ... praktisch.
    Weil es praktisch kaum managebar ist sämtliche Exception-Typen aufzulisten die etwas werfen kann. Ob mit oder ohne Sprachsupport spielt dabei kaum eine Rolle.

    Was das "Exception vergessen zu behandeln" angeht: hier nähern wir uns schön langsam dem eigentlichen Problem: nämlich dass die Klassenhierarchien der Exception von Java, C++ und C# total beknackt sind, und C++ es obendrein noch erlaubt beliebige Klassen zu werfen.
    Das sehe ich auch als Problem. Es führt aber nicht die ganze Exception-Sache ad Absurdum, sondern macht lediglich den Umgang mit Exceptions in C++ etwas lästig.

    Vielleicht checked Exceptions an API Boundaries. Also "mir egal was du intern machst, solange du mir sagst, was ich behandeln muss".



  • Kellerautomat schrieb:

    hustbaer schrieb:

    Kellerautomat schrieb:

    hustbaer schrieb:

    Kellerautomat schrieb:

    shared_ptr: Hab ich persoenlich noch nie gebraucht, kann also nicht nachvollziehen was das bringen soll. Und natuerlich kann man mit RAII use-after-free Fehler bauen. Das verhindert man am besten, indem man ein API ordentlich designed.

    Uiiiiiii. Wieder kein Argument. Diesmal nicht weil der Zusammenhang fehlt, sondern weil es einfach nur eine Behauptung ist. Der ich widerspreche: geht halt nicht immer. Und je nachdem was man so programmiert kann "nicht immer" sehr oft sein.

    Zeig mir die Faelle, wo du glaubst shared_ptr zu brauchen. Ich zeig dir, warum du ihn nicht brauchst.

    OMG, du bist echt stur. Oder unerfahren. Oder beides.

    Ein Programm kommuniziert über diverse Transport Channels (RS-232, TCP/IP Verbindung, HTTP Verbindung, ...) mit diversen Endpoints (Web-Services, Periphäriegeräte die an einem gemeinsamen Bus hängen oder verschiedenen Funktionsblöcken in diesen Geräten etc.).
    Wichtig ist nur: es gibt mehrere Endpoints die über den selben Channel angesprochen werden müssen.
    Gibt genügend reale Beispiele dafür, musst du mir im Zweifelsfall einfach glauben.
    Und da teilen sich alle Objekte die eine Verbindung zu einem Endpoint darstellen halt einfach die Ownership des Channel Objekts.

    Thread-safe Signals.
    Wenn dir das Problem nicht bekannt ist, dann lies dir die Doku zu Boost.Signals2 durch, da wird es ausführlich erklärt:
    http://www.boost.org/doc/libs/1_56_0/doc/html/signals2/thread-safety.html#idp431602368
    Der Knackpunkt ist: Das Signal muss sicherstellen dass der Slot nicht gelöscht wird während er gerade aufgerufen wird. Das Signal muss also während des Aufrufs "owner" des Slots sein. Gleichzeitig muss aber natürlich der der den Slot connected hat auch "Owner" des Slots sein, da der Slot ja sonst sofort wieder zerstört ("dicsonnected") würde.

    Billig kopierbare immutable Objekte mit Hilfe von shared_ptr<T const> bauen.
    Ist ne "Optimierung", aber eine sehr gute. Ist einfach, effizient und sicher.

    ...

    Ich hatte gehofft, du kommst mir jetzt nicht mit solchen kleinen Details (#2/#3) sondern mit richtigen Szenarien. Bei #1 wuerde ich einfach sicherstellen, dass der Channel laenger lebt.

    Ne, so einfach geht das jetzt nicht.
    Das sind richtige Szenarien.
    Und wie willst du sicherstellen dass der Channel länger lebt. Und wie willst du ohne shared_ptr (oder ähnliche Tools) garantieren dass kein use after free passiert.

    Ich will eine nachvollziehbare Erklärung für alle drei Szenarien.
    Denn alle drei hab' ich schonmal implementiert - die meisten davon mehr als 1x.



  • hustbaer schrieb:

    Ne, so einfach geht das jetzt nicht.
    Das sind richtige Szenarien.
    Und wie willst du sicherstellen dass der Channel länger lebt. Und wie willst du ohne shared_ptr (oder ähnliche Tools) garantieren dass kein use after free passiert.

    Ich will eine nachvollziehbare Erklärung für alle drei Szenarien.
    Denn alle drei hab' ich schonmal implementiert - die meisten davon mehr als 1x.

    struct System
    {
            Channel channel;
            vector<Endpoint> endpoints;
    };
    

    Hmja, muss schon sagen: Auf shared_ptr verzichten ist echt hart.



  • Ja weisste, doof stellen kann ich mich auch.



  • Erklaer doch mal, was an der Loesung nicht passt. Duerfte so ziemlich genau tun, was du erklaert hast. Oder du hast einfach die Anforderungen nicht genau genug erklaert :p



  • Die Endpoint-Connection Objekte werden natürlich an verschiedene andere Objekte übergeben (=Ownership-Transfer), die sich so gar nicht darum scheren wie die Endpoint-Connections implementiert sind, und dass die evtl. einen gemeinsam benutzten Channel haben.
    Genau das ist nämlich vernünftiges API Design.

    Weitere Beispiele:

    Ein Video-Control (GUI-Control, "Widget") das einen "Renderer" braucht um sein Video auf den Schirm zu klatschen. Der Renderer verwendet z.B. D3D oder OpenGL, und du musst 20+ solche Video Controls gleichzeitig unterstützen. In Fenstern die der User nach belieben auf und zumachen kann.
    Die Video-Controls sollten sich also sinnvollerweise einen Renderer teilen, da der Renderer ein nicht ganz billiges Objekt ist (z.B. ein D3DDevice/OGL-Context, ein Shader, mehrere Temp-Texturen).

    Die Videos kommen von Kameras. (Ist jetzt kein eigener Punkt mehr, sondern ein Beispiel für (1)). Die Kameras unterstützen nur eine begrenzte Anzahl an gleichzeitigen "User-Sessions". Man kann also nicht für jeden Scheiss eine eigene aufmachen. Eine User-Session ist nötig um das Livebild zu bekommen und genau so ist eine User-Session nötig um z.B. Bewegungsalarme zu empfangen. Und natürlich um eine Aufzeichnung zu starten, die Kamera zu steuern etc.
    Dummerweise war es dem Entwickler der Kamera-API aber zu doof hier selbst Shared Ownership zu implementieren, und dafür zu sorgen dass z.B. die Livebild-Connection auch die User-Session am Leben hält. Machst du die User-Session zu, "stirbt" damit auch die Livebild-Connection.
    Deine Livebild-Connection Klassen müssen sich also Ownership des User-Session-Handles teilen. Und zwar möglicherweise untereinander (es gibt Fälle wo mehrere Livebild-Connection zur selben Kamera nötig sind, z.B. mit unterschiedlichen Auflösungen/Formaten), als auch mit der User-Session Klasse selbst, die anderorts benötigt wird um eben die Alarme reinzubekommen oder die Kamera zu steuern (bewegen/zoomen/...).
    Und "anderorts" sind einmal Services die im Hintergrund laufen, und dann wieder andere Fenster, die der User auch wieder nach Belieben auf- und zumachen kann.

    Inversion of Control mit nem Service-Locator.
    Da sind verschiedene Services drin registriert, die sich auch untereinander verwenden, und weder der IOC-Container noch der Service-Locator können wissen in welcher Reihenfolge die ganze Show zerstört werden muss damit es kein "use after free" gibt.

    Caches.

    Du hast ne Anwendung in der du irgendwelche "Dokumente" suchen kannst. Ob als headless Service oder GUI ist im Prinzip egal. Der bzw. die User können da Suchanfragen schicken. Die Ergebnisse kommen aus einer Search-Index Klasse die das übernimmt. Dahinter steht keine klassische Datenbank-Connection mit Transaktionen und allem, sondern z.B. ein Lucene Index.
    Hin und wieder muss der Index aktualisiert werden. Das passiert im Hintergrund indem einfach ein neuer Index aufgebaut wird, weil das die eleganteste und auch performanteste Lösung ist. Der neue Index wird parallel zum alten aufgebaut, und wenn er fertig ist soll der alte weggeworfen werden und ab da dann der neue verwendet.
    Die Suchergebnisse die der Index ausspuckt sind aber bloss eine Liste von IDs. Die Daten selbst müssen über die Dokument-ID aus dem Index bezogen werden. Alle Daten über alle Dokumente sofort abzufragen ist nicht vertretbar, da es viel zu lange dauern würde. Der User sieht immer nur ein paar wenige auf einmal so lange er nicht "blättert".
    Die Suchergebnisse müssen aber gültig bleiben während der User drinnen rumstöbert. Die Suchergebnisse müssen also den Index am Leben halten bis er nicht mehr benötigt wird.

    Natürlich wäre es möglich die Suche beim blättern jedes mal zu wiederholen, aber das hat ein paar unerwünschte Nebeneffekte - z.B. dass der User bestimmte Ergebnisse nie sieht, weil sie zwischen der Anfrage für Seite 1 und der für Seite 2 von Seite 2 auf Seite 1 "verrutscht" sind. Und natürlich ist die Performance u.U. deutlich schlechter.
    Aber angenommen damit kannst du leben ... ergibt sich dann gleich das nächste Problem: du kannst den alten Index nicht gegen den neuen austauschen so lange noch irgendwelche Threads IN einer Suchfunktion mit dem alten Index stecken. D.h. du brauchst erst wieder Shared-Ownership um den alten Index so lange am Leben zu halten wie es Threads gibt die darauf zugreifen. ODER du musst über eine zentrale Reader-Writer-Mutex sicherstellen dass keine Reader mehr auf den Index zugreifen während du ihn auswechselst. Das blockiert neue Anfragen so lange bis alle die gerade "in Ausführung" sind fertig gelaufen sind.
    Klar, damit kann man u.U. leben, aber es sind zwei Nachteile die man nicht haben müsste, wenn man einfach akzeptiert dass Shared-Ownership etwas ganz normales ist, und man nicht dagegen ankämpfen sollte.
    Und lustigerweise wirst du selbst damit die Shared-Ownership nicht ganz los - du verschiebst sie bloss in die Reader-Writer-Mutex, so dass du sie selbst "nicht mehr sehen musst". Denn die "Read-Locks" auf die Reader-Writer-Mutex die die Abfrage-Threads halten müssen sind auch nix anderes als Shared-Ownership. (In C++ heisst die Read-Lock Klasse sinnvollerweise sogar std::shared_lock ).

    GUI bzw. 3D Engines. Hier gibt es zwei mir bekannte sinnvolle Varianten. Die eine ist einfach, und verwendet Shared-Ownership für Dinge wie Texturen, Shader, Geometry etc. (die 3D Engines die ich kenne machen das so). Die andere ist deutlich aufwendiger und verwendet Resource-Manager Klassen von denen die nötigen Resourcen jedes mal wenn sie benötigt werden anhand ihrer ID neu angefordert werden.



  • TyRoXx schrieb:

    C# fällt weg, weil Mono unter aller Sau buggy ist. Visual Studio ist einfach lächerlich zurückgeblieben.
    Apple-Müllsprachen fallen natürlich auch weg.
    Java ist scheiße und tot. Es gibt keine vernünftige Entwicklungsumgebung. Soll ich ernsthaft eine Programmiersprache einsetzen, die standardmäßig Malware ("Toolbars") mitliefert?
    Ich benutze C++, weil es im Gegensatz zu anderen Sprachen funktioniert. Es gibt zwei brauchbare Compiler (GCC, Clang), eine brauchbare IDE (QtCreator) und kann zur Not ohne IDE sinnvoll benutzt werden.

    Hast du gesoffen?



  • hustbaer schrieb:

    Inversion of Control mit nem Service-Locator.
    Da sind verschiedene Services drin registriert, die sich auch untereinander verwenden, und weder der IOC-Container noch der Service-Locator können wissen in welcher Reihenfolge die ganze Show zerstört werden muss damit es kein "use after free" gibt.

    Das macht aber auch keinen Spaß ohne einen GC. Außer du beweist immer, daß es keine zirkulären Abhängigkeiten gibt.



  • Ethon schrieb:

    TyRoXx schrieb:

    C# fällt weg, weil Mono unter aller Sau buggy ist. Visual Studio ist einfach lächerlich zurückgeblieben.
    Apple-Müllsprachen fallen natürlich auch weg.
    Java ist scheiße und tot. Es gibt keine vernünftige Entwicklungsumgebung. Soll ich ernsthaft eine Programmiersprache einsetzen, die standardmäßig Malware ("Toolbars") mitliefert?
    Ich benutze C++, weil es im Gegensatz zu anderen Sprachen funktioniert. Es gibt zwei brauchbare Compiler (GCC, Clang), eine brauchbare IDE (QtCreator) und kann zur Not ohne IDE sinnvoll benutzt werden.

    Hast du gesoffen?

    Zumindest den Teil mit der Toolbar kann ich nachvollziehen.



  • Also ne Toolbar mußte ich bei Java nie mitinstallieren. Ich bin wirklich kein Java-Fan, aber fair sollte man schon noch bleiben. 🙄



  • Artchi schrieb:

    Also ne Toolbar mußte ich bei Java nie mitinstallieren. Ich bin wirklich kein Java-Fan, aber fair sollte man schon noch bleiben. 🙄

    Gemeint ist der nicht versteckte Installer für Windows. Es gibt noch andere, die keine Malware installieren.


Anmelden zum Antworten