Vortrag über Ideen für eine neue Sprache für Spieleentwickler [youtube]
-
volkard schrieb:
Und WIE machst Du use-after-free-Fehler? Der Destruktor ruft free auf. Danach ist das Objekt WEG.
Das geht so einfach nur in Bilderbuchbeispielen, wo immer alles "unique ownership" ist und es nie ausgeborgte, nicht-besitzende Zeiger oder Referenzen gibt.
Sobald es die gibt, kann man Mist bauen.
Heisst: das Objekt das die Resource besitzt mag zwar weg sein, aber das heisst noch lange nicht dass keiner mehr nen Zeiger bzw. ne Referenz mehr darauf hat.
D.h. du kannst "use after free" der Resource gegen "use after free" des RAII-Resourcen-Wrappers eintauschen. Aber verhindern kannst du es so nicht.Gerade da hier im Forum (und auch andrerorts) immer rumgeheult wird wie phöse doch
shared_ptr
ist, und dass man ihn ja fast nie braucht...
Und wenn man versucht so weit wie möglich aufshared_ptr
zu verzichten dauert es nicht lange bis man sich wieder in den Fuss schiesst.Was u.A. ein Grund ist warum ich
shared_ptr
sehr freizügig verwende. Weil mir die Probleme die man durch zu vielshared_ptr
bekommt lieber sind als die die man durch zu wenigshared_ptr
bekommt.
-
Ich denke, der Typ hat im grossen und ganzen Recht. Zwei Hauptpunkte, in denen ich absolut nicht zustimme: RAII und Funktionsparameter
RAII ist einfach praktisch, auch ohne Exceptions (die ich im Uebrigen auch fuer Mist halte). Ich will einfach kein free/dealloc/etc hinschreiben muessen.
Funktionen sollten nicht mehr als 4 Parameter haben. Die Beispiele mit 10+ Parametern sind einfach Mist. Named Parameters sind aber natuerlich trotzdem nett.
Ansonsten hat er meine volle Zustimmung, wenn ich nichts vergessen habe.
-
kkaw schrieb:
Meine anderen Aliase sind "Sebastian Pizer" und "krümelkacker".
Das sind allerdings große Namen, denen ich keine Kompetenz abspreche.
kkaw schrieb:
Nee, ich habe mit use-after-free auch keine Probleme. Aber ich bilde mir nicht ein, alle Anwendungsdomänen zu überblicken.
Hmm. Da gibt es dann noch die Leute, die voller Menschenliebe für andere Sachen erfinden, die sie selber nicht brauchen, wie sicheres Spielwerkzeug, Lehrsprachen oder Religionen.
Hmm, wo hatte ich mal Probleme mit der Löschverantwortung? Vor fast 15 Jahren, hab da simulierte Netzwerkpakete zeitversetzt geschickt, also der Sender musste sie loslassen. Und die Senke war vorher nicht klar, im Laufe der Verarbeitung konnten sie sich auch mal ändern oder gar duplizieren. War natürlich lösbar, auch wenn ich es heute anders machen würde.
kkaw schrieb:
Gerade bei so GUI Zeugs, könnte das gut vorkommen, wenn man nicht überall shared_ptr benutzen will/kann.
Kann sein. Ich schreibe nicht viel Oberflächencode. Bisher keine Probleme. Schlimmstenfalls verknüpft man GUI-Elelente eben mit shared_ptrs, wie Du es großzügig machst.
kkaw schrieb:
Ja genau das meine ich. Du denkst ich bin ein "C-in-C++" Programmierer oder so ähnlich, der von RAII und was man damit machen kann, keine Ahnung hat etc etc etc. Wahnsinnige Kognitionsleistung von Dir ...
Du stellst Dich anscheinend als Verteidiger der "C-in-C++"-Programmierer hin, sagst die typischen C-Fehler seien in C++ voll normal. Ist wohl klar, wie das wirkt.
-
Ich moechte nochmal etwas genauer auf die einzelnen Punkte eingehen.
RAII: Was der Typ offensichtlich nicht rafft, ist dass RAII auch ohne Exceptions super nuetzlich ist. Dass RAII Wrapper nervig zu schreiben sind, ist denke ich mal kein Geheimnis und finde ich auch stoerend. Man kann aber mit einer Helfer-Klasse Abhilfe schaffen (ich vermute dass scoped_resource aus C++14/17 sowas ist?)
Exceptions: Vermutlich eine der schlechtesten Erfindungen in der Geschichte der Programmiersprachen. Es gibt so ziemlich keinen Fall, in dem ich eine Exception verwenden wuerde. Out of bounds? Da soll das Programm crashen, weil das ein Bug ist. File konnte nicht geoeffnet werden? Das ist doch vollkommen erwartet. Zu wenig Speicher? Error-Message & terminieren. Es gibt einfach keine Faelle, in denen Exceptions irgendwie sinnvoll sind. Halt, doch. Da war doch was mit Konstruktoren. Hm, vielleicht einfach Konstruktoren entfernen und Funktionen bereitstellen? Koennte klappen. Oder einfach nix im Konstruktor machen, was fehlschlagen koennte. Hey, das klingt doch gut.
Kurzform fuer unique_ptr? Immer her damit! Aber nicht so. unique_ptr in der stdlib ist gut so, aber man kann ja auch Klassen erlauben, Typ-Konstruktoren zu erfinden. Ich nenne es mal custom declarators. Also T! -> unique_ptr<T> und T? -> optional<T>. Dass der Compiler das benutzen koennte, um schnelleren Code zu erzeugen, oder fuer bessere Fehlermeldungen ist doch Schwachsinn. C++ ist einfach zu kompliziert. In einer simplen Sprache gibts das Problem gar nicht, behaupte ich einfach mal. Dass der Pointer-Typ ein Attribut vom T sein sollte und nicht umgekehrt, da hat er Recht.
Joint Arrays: Joa, das finde ich gut. In Spielen muss man dauernd Daten auf die GPU uploaden, da waere sowas ultra praktisch. In Kombination mit Introspection koennte man auch vermutlich recht schoen die erforderlichen OpenGL/DX Calls automatisch generieren. Einzig was mich daran stoert in seiner praesentierten Form, ist dass man ein "Hauptattribut" waehlen muss, das mit allen anderen gejoined wird. Das ist Syntax-maessig einfach seltsam.
Named Parameters: Sicherlich praktisch, aber meiner Meinung nach sollte keine Funktion ueber 4 Parameter haben. 4 ist hierbei ein Hard Limit. Exakt 4, nicht 5. Oft kann man Parameter recht schoen zusammenfassen. Ein void*/size_t Paar in eine MemoryRegion oder so. Statt begin/end Iterator eine Range. Und so weiter. Ausserdem sollte man nicht ausschliesslich Named Parameters haben. Bei einem sin() moechte ich da keinen Namen hinschreiben muessen. Was waere ueberhaupt ein sinnvoller Name? x? value? Macht einfach keinen Sinn.
Fragmentierung: Halte ich fuer kompletten Bullshit. Das Problem ist, dass dynamische Arrays heute nicht die richtigen Allokationsstrategien verwenden. Wenn ich einen vector habe, dann ist erwartet, dass da eine Menge Objekte reinkommen. Und wenn ich eine Menge Objekte habe, die linear im Speicher liegen, dann kann ich auch einfach nur ganze Pages allozieren, da brauch ich keinen fancy Allokator mit Free-Lists, Buddy System und was weiss der Teufel. Die paar Bytes am Ende des Vektors die ungenutzt sind interessieren Keinen, der Vektor ueberalloziert doch sowieso.
Will ich viele kleine Vektoren, dann sieht die Sache natuerlich etwas anders aus, aber das ist dann auch ein speziellerer Use-Case. Vielleicht zusaetzlich eine small_vector Klasse oder sowas.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. Aber unmoeglich ist es dadurch natuerlich nicht.
Hab bestimmt was vergessen, werde aber das Video zuhause nochmal ueberfliegen.
Gruesse
Der Kellerautomat
-
Kellerautomat schrieb:
Zu wenig Speicher? Error-Message & terminieren.
Nee. Wie meine Oma immer sagt, wenn sie aus dem Lift aussteigt und auf den Knopf drückt, was man einschaltet muss man auch wieder ausschalten.
Dein exit passt fast immer. Das Betriebssystem räumt Speicher und Sockets und alles auf. Passt aber nicht immer. Genug Socket-Gegner erwarten, daß man nur einen kurzes disco hatte und halten die Session offen. Bei Wiederprogrammstart ne neue Session und die max 10 Sessions sind schnell weg. Der Destruktor hätte auf natürliche Weise dem Gegner gesagt, daß man diese *entfernte* Resource nicht mehr benötigt.
Ok, ist ein konstruiertes Beispiel. Vorgekommen ist es mir unglaublich störend auf Win95, wo Locks aus Dateien nicht verschwanden, wenn der lockende Prozess starb. Also bei jedem Programmierfehler neu booten. Und bei Datenbanken, die nur 10 Sitzungen zuließen.
Behoben habe ich das mit einem eigenen ASSERT-Makro, das mich erstmal in den Debugger wirft, damit ich fein schauen kann, was da schief ging, und der wenn ich dann F5 drückte nicht nach exit() ging, sondern nach throw.Ist aber lange her, daß ich sowas benutze. Damals war ich noch Nube. In der Tat benutze ich Exceptions heute nur, um dem Benutzer zu sagen, daß was richtig schlimmes passiert ist, was er eh nicht beheben kann.
Vielleicht die Exceptions wegwerfen aber noch ein Konzept lassen, daß man seinen Netzwerkgegner ordentlich verabschieden kann. An Objekte?/Scopes? einen Exiter dranhängen können, der zuschlägt, wenn DIE Exception fliegt.
Kellerautomat schrieb:
Es gibt einfach keine Faelle, in denen Exceptions irgendwie sinnvoll sind. Halt, doch. Da war doch was mit Konstruktoren. Hm, vielleicht einfach Konstruktoren entfernen und Funktionen bereitstellen? Koennte klappen. Oder einfach nix im Konstruktor machen, was fehlschlagen koennte. Hey, das klingt doch gut.
Gefällt mir. Die Sonderrolle der Konstruktoren war mir immer zuwider.
-
volkard schrieb:
Nee. Wie meine Oma immer sagt, wenn sie aus dem Lift aussteigt und auf den Knopf drückt, was man einschaltet muss man auch wieder ausschalten.
Dein exit passt fast immer. Das Betriebssystem räumt Speicher und Sockets und alles auf. Passt aber nicht immer. Genug Socket-Gegner erwarten, daß man nur einen kurzes disco hatte und halten die Session offen. Bei Wiederprogrammstart ne neue Session und die max 10 Sessions sind schnell weg. Der Destruktor hätte auf natürliche Weise dem Gegner gesagt, daß man diese *entfernte* Resource nicht mehr benötigt.
Ok, ist ein konstruiertes Beispiel. Vorgekommen ist es mir unglaublich störend auf Win95, wo Locks aus Dateien nicht verschwanden, wenn der lockende Prozess starb. Also bei jedem Programmierfehler neu booten. Und bei Datenbanken, die nur 10 Sitzungen zuließen.
Behoben habe ich das mit einem eigenen ASSERT-Makro, das mich erstmal in den Debugger wirft, damit ich fein schauen kann, was da schief ging, und der wenn ich dann F5 drückte nicht nach exit() ging, sondern nach throw.Ist aber lange her, daß ich sowas benutze. Damals war ich noch Nube. In der Tat benutze ich Exceptions heute nur, um dem Benutzer zu sagen, daß was richtig schlimmes passiert ist, was er eh nicht beheben kann.
Vielleicht die Exceptions wegwerfen aber noch ein Konzept lassen, daß man seinen Netzwerkgegner ordentlich verabschieden kann. An Objekte?/Scopes? einen Exiter dranhängen können, der zuschlägt, wenn DIE Exception fliegt.
Hmm. Also sowas wie in D. Hoert sich gut an. Ich verwende Exceptions auch so wie du.
Vielleicht eine Art terminate Statement statt throw, dem man den exit-Code mitgeben kann:
if(!allocateStuff()) terminate -1;
Kein try/catch, nur scope(exit/success/failure). Destruktoren weg, stattdessen kann man diese 3 Bloecke auch in Klassen schreiben. Also quasi bessere Destruktoren.
volkard schrieb:
Gefällt mir. Die Sonderrolle der Konstruktoren war mir immer zuwider.
Andererseits, fuer Sachen wie mathematische Vektoren ist es doch ganz praktisch, wenn ich vector3f(1.f, 2.f, 3.f) schreiben kann. Hmmmm...
-
Kellerautomat schrieb:
Exceptions: Vermutlich eine der schlechtesten Erfindungen in der Geschichte der Programmiersprachen. Es gibt so ziemlich keinen Fall, in dem ich eine Exception verwenden wuerde.
Ich weiß nicht, seh ich erstmal nicht so eindeutig. Ich verwende zwar auch keine Exceptions, ganz sicher bin ich mir aber nicht, was das betrifft. Woran ich denke, sind Rückgabewerte bei verschachtelten Funktionen. Wenn man eine Klasse C benutzt, die über 20 Funktionsaufrufe irgendwo Funktion X aufruft, und Funktion X will einen Fehler zurückgeben, dann wärs evtl. besser, da eine Exception zu schmeißen, als den Fehlercode durch 20 Funktionen durchzuschleifen. Kann ja sein, dass die sich überhaupt nicht für den Fehler interessieren, aber der Aufrufer der Klasse C könnte damit was anfangen und dann was anderes machen.
Wie gesagt, hab sowas noch nie benutzt und würde ich auch nicht machen, würde mich aber auch nicht trauen zu behaupten, dass es keinen Sinn macht oder schlecht wäre.
-
Kellerautomat schrieb:
Andererseits, fuer Sachen wie mathematische Vektoren ist es doch ganz praktisch, wenn ich vector3f(1.f, 2.f, 3.f) schreiben kann. Hmmmm...
Ja, vector3f(1.f, 2.f, 3.f) ist toll, ist nur Aufrufmagie. Die andere Seite stört mich. Konstruktoren sind static-Methoden, ohne daß ein static da steht. Sie haben einen seltsamen Namen, statt wie in anderen Sprachen new, _construct oder so. Die haben keinen Rückgabetyp, nichtmal void.
-
Mechanics schrieb:
Ich weiß nicht, seh ich erstmal nicht so eindeutig. Ich verwende zwar auch keine Exceptions, ganz sicher bin ich mir aber nicht, was das betrifft. Woran ich denke, sind Rückgabewerte bei verschachtelten Funktionen.
Exceptions sind voellig ueberdesigned mit beliebig schmeissbaren Typen, die per Polymorphie abgefangen werden, wobei man auch wieder aufpassen muss, dass die Exception keine Exception ausloest, dafuer gibt es dann viele Keywords, zusaetzliche Blocke, usw.
Es wuerde reichen, wenn man eine Exceptionart fuer schwerwiegende Laufzeitfehler anbietet, die dann z.B. einfach nur einen String schmeisst und nicht mehr. Bei lua wird z.B. einfach die Funktion error benutzt, die sowieso schon vom Interpreter fuer Fehler angeboten wird. Dazu gibt es pcall, mit dem eine Funktion gesichert aufruft und das dann ein Error-flag und Fehlermeldung/Rueckgabewert zurueckgibt.
In Rust ist es aehnlich. Es gibt Makros wie fail! und assert! und wenn sie ausloesen, wird der Task (vergleichbar mit std::asynch) beendet.
Es gibt aber kaum Gruende, warum man auf klare Programmfehler wie out-of-bound-Errors mit einer Exception reagieren sollte, denn die einzie sinnvolle Reaktion auf solche Fehler ist ein Bugreport an den Entwickler.
-
Wäre für Spieleprogrammierung nichtmal eine Sprache interessant die das Entity-Component Modell anstatt Objektorientierung umsetzt?
Ich weiß nicht wie man designen könnte und ob man auf Objektorientung verzichten könnte aber irgendwie finde ich die Idee interessant.
-
Marthog schrieb:
Es wuerde reichen, wenn man eine Exceptionart fuer schwerwiegende Laufzeitfehler anbietet, die dann z.B. einfach nur einen String schmeisst und nicht mehr.
Naja, ein std::string wäre nett für "exit: Konnte Datei 'C:\autoexec.bat' nicht finden.". Damit der Werfer die eine Meldung wenigstens basteln kann.
Ein char cont* würde mir aber zu 90% der Fälle reichen. 90% ist zu wenig.
Ein int(1542="file not found, oder so) ist mir auch zu wenig.
std::string ist mir aber auch wieder zu beengend, Sprachmittel sollten ohne die std::-lib auskommen, zum Beispiel für Microcontroller.
Hab geheult, als ich versuchte, C++ auf einen µC zu machen (GCC): Die erste virtuelle Funktion erzeugt die Möglichkeit eines purecalls. Daraufhin hab ich im RAM 30k reservierten Exceptionspeicher und im CODE 350k die <iostream>-lib für eine eventuelle Ausgabe von "purecall".
Vielleicht sollte man Lamdas werfen. Wo die sich ihren Stringspeicher besorgen, falls nötig, ist doch ihr Problem. Nee, geht auch nicht, Lamdas sind nicht Größenbeschränkt.
Denke ein char const* reicht. Normalerweise schickt man Adressen von Stringliteralen. Wer mag, kann sich globalen Speicher (256 Byte?) reservieren, da reinschreiben und schicken. Wer mag, kann dort Zeiger auf Lamdas ablegen.Daß die std-lib überhaupt Exceptions wirft, ist vielleicht ein Fehler. Außer bad_alloc sehe ich es nicht ganz ein.
And now for something comletely different:
iostream ist ugly.
Alsoint i; cin<<i;
ist doch zum Kotzen. Es muss heißen
int i(cin);
nebst
class Foo{ Bar b; int i; float f; public: Foo(ifstream& in): b(in),i(in),f(in){ } … };
Oh, da müssen Exceptions her. Uih, sogar schwächere als nur exit-Exceptions. Daß das Programm gleich exitet, weil ein Formatfehler in einer Datei war? Neee.
Hab mal für ein MMRPG so einen Lader/Speicherer in C++ gebaut, der genau wie oben aussah. Das Format war JSON-ähnlich, hab bei Stings und Arrays vorher die Länge geschickt. Gegen die alte C-Implementierung hatte ich ca Faktor 180 rausgeholt. Das geht halt toll, weil man sich bei einer Exception-Sprache nur auf das Wesentliche konzentrieren muss. Kein Objekt oder Subobjekt muss sich je drum kümmern. Nur die elementaren Leser werfen bei Fehlformat was. Nur die ganz fette readSaveGame() fängt. Zwischenduch kein if, keine überladenen Returnwerte, keine globalen Fehlerflags, keine std::pair<success,Data>.
Mist. Struppi hatte recht. Ich will die Möglichkeit haben, zwischenduch abzufangen, und dem Looser die Wahl zu geben, eine andere Datei zu öffnen.
Hab jetzt Lust, daß gefordert werden würde, daß alle Erben von std::exception nicht größer sind. Mhhm, geht wohl nicht?
-
@Kellerautomat
Echt jetzt?
Du willst an jeder Stelle wo etwas erwarteterweise schief gehen kann immer und immer wieder die dämlichen Fehlerchecks wiederholen?
Function-Chaining adé?Wie machst du das dann, alle Factory-Funktionen geben nen
unique_ptr
zurück dernull
sein könnte? Und Fehlerwert dann per Output-Parameter? Oder Fehler als Returnwert (int?) und das eigentliche Objekt dann per Output-Parameter?
Oder bekommt jede Klasse nen Zombie-Zustand?Und bei
std::string("blub") + std::string(LeiderEinZombie)
kommt dann alaAnything + NaN => NaN
wieder ein Zombie raus. Oder ...?Sorry, aber ne.
Bei solchen Aussagen frage ich mich immer ernsthaft ob diejenigen die sie bringen schonmal wirklich ein Programm geschrieben haben. Also mehr als
puts("Hello world.");
.Auf Exceptions zu verzichten kann schonmal Sinn machen - WENN man einen guten Grund dafür hat. Aber Exceptions einfach so als Unsinn abzutun ist einfach nur Unsinn.
-
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.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.
-
Ui, da hab ich aber viele Gefuehle verletzt
Marthog schrieb:
Exceptions sind voellig ueberdesigned mit beliebig schmeissbaren Typen, die per Polymorphie abgefangen werden, wobei man auch wieder aufpassen muss, dass die Exception keine Exception ausloest, dafuer gibt es dann viele Keywords, zusaetzliche Blocke, usw.
Ich will mal diesen Satz hervorheben. Das ist ziemlich genau, was ich mir denke.
volkard schrieb:
Daß die std-lib überhaupt Exceptions wirft, ist vielleicht ein Fehler. Außer bad_alloc sehe ich es nicht ganz ein.
+1
volkard schrieb:
And now for something comletely different:
iostream ist ugly.
Alsoint i; cin<<i;
ist doch zum Kotzen. Es muss heißen
int i(cin);
nebst
class Foo{ Bar b; int i; float f; public: Foo(ifstream& in): b(in),i(in),f(in){ } … };
Neee, so doch nich. Viel zu wenig generisch. Wir brauchen ein (automatisch generiertes) fmap.
struct Foo { int i; double d; }; // vom compiler generiert template <typename F> void fmap(Foo& foo, F&& f) { f(foo.i); f(foo.d); } // alles rekursiv abarbeiten struct Read { Read(istream& is) : is_(&is) {} void operator () (int& i) { *is_ >> i; } void operator () (double& d) { *is_ >> d; } // usw. ... template <typename T> void operator () (T& obj) { fmap(obj, *this); } private: istream* is_; }; template <typename T> void read(istream& is, T& obj) { fmap(obj, Read(is)); } read(is, obj);
Ach Mist, jetzt sind wir ja wieder, wo wir vorher waren. Also noch ein kleines Helferlein.
template <typename T> T readFromStream(istream& is) { T x; read(is, x); return x; } auto x = readFromStream<Foo>(cin);
Na geht doch. Und ich muss keine istream Konstruktoren mehr definieren. Und fmap kann ich auch fuer anderes verwenden, z.B. einen operator < und Konsorten. Zwei Fliegen mit einer Klatsche.
volkard schrieb:
Oh, da müssen Exceptions her. Uih, sogar schwächere als nur exit-Exceptions. Daß das Programm gleich exitet, weil ein Formatfehler in einer Datei war? Neee.
Hab mal für ein MMRPG so einen Lader/Speicherer in C++ gebaut, der genau wie oben aussah. Das Format war JSON-ähnlich, hab bei Stings und Arrays vorher die Länge geschickt. Gegen die alte C-Implementierung hatte ich ca Faktor 180 rausgeholt. Das geht halt toll, weil man sich bei einer Exception-Sprache nur auf das Wesentliche konzentrieren muss. Kein Objekt oder Subobjekt muss sich je drum kümmern. Nur die elementaren Leser werfen bei Fehlformat was. Nur die ganz fette readSaveGame() fängt. Zwischenduch kein if, keine überladenen Returnwerte, keine globalen Fehlerflags, keine std::pair<success,Data>.
Mist. Struppi hatte recht. Ich will die Möglichkeit haben, zwischenduch abzufangen, und dem Looser die Wahl zu geben, eine andere Datei zu öffnen.
Hab jetzt Lust, daß gefordert werden würde, daß alle Erben von std::exception nicht größer sind. Mhhm, geht wohl nicht?
Ok, ich gestehe. Fuer sowas hab ich auch schon Exceptions verwendet. Aber vielleicht koennte man etwas bauen, das aehnlich funktioniert, ohne gleich Exceptions zu brauchen. Ein Sprung zum naechsten Error-Handler oder so?
hustbaer schrieb:
@Kellerautomat
Echt jetzt?
Du willst an jeder Stelle wo etwas erwarteterweise schief gehen kann immer und immer wieder die dämlichen Fehlerchecks wiederholen?
Function-Chaining adé?Wie machst du das dann, alle Factory-Funktionen geben nen
unique_ptr
zurück dernull
sein könnte? Und Fehlerwert dann per Output-Parameter? Oder Fehler als Returnwert (int?) und das eigentliche Objekt dann per Output-Parameter?
Oder bekommt jede Klasse nen Zombie-Zustand?Und bei
std::string("blub") + std::string(LeiderEinZombie)
kommt dann alaAnything + NaN => NaN
wieder ein Zombie raus. Oder ...?Sorry, aber ne.
Bei solchen Aussagen frage ich mich immer ernsthaft ob diejenigen die sie bringen schonmal wirklich ein Programm geschrieben haben. Also mehr als
puts("Hello world.");
.Guck dir mal an, wie sowas in Haskell gemacht wird. Das ist elegant. Stichwort Maybe/Either.
Zu deinem std::string Beispiel sag ich einfach mal nix, da du offensichtlich meine Posts garnicht richtig gelesen hast. Du findest die Antwort dort.hustbaer schrieb:
Auf Exceptions zu verzichten kann schonmal Sinn machen - WENN man einen guten Grund dafür hat. Aber Exceptions einfach so als Unsinn abzutun ist einfach nur Unsinn.
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.
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.
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.
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.
-
Ich finde Exceptions vor allem hässlich, aber nicht überflüssig. In C-Manier "if (return-Val == Fehler-Code)" finde ich schlimmer.
Was ich schlimmer finde, ist null. Hätte nie erfunden werden dürfen. Darauf zu testen ist hässlich und falls man es vergisst, kann das Programm abstürzen.
Scala kennt Null nur von Java, benutzt es in eigenen Libs allerdings nicht. Es wird höchstens None (eher bei Listen. Alle Operationen sind jedoch noch anwendbar.) zurückgegeben oder ein bestimmter Fehlertyp (bei der Verarbeitung von Daten).
Z. B.:request.body.validate[User] match { case jsUser: JsSuccess[User] => // Do something case error: JsError => // Error Handling }
Allgemeine Fehlerbehandlung (diese Konstellation taucht eher selten auf):
request.body.validate[User] match { case jsUser: JsSuccess[User] => // Do something case _ => // General Error Handling }
L. G.,
IBV
-
Kellerautomat schrieb:
template <typename T> T readFromStream(istream& is) { T x; read(is, x); return x; }
Nee. Serialisierter, die Standdardkonstruktoren verlangen, sind für mich indiskutabel.
-
volkard schrieb:
Nee. Serialisierter, die Standdardkonstruktoren verlangen, sind für mich indiskutabel.
Gib mal ein Beispiel von einem Typen, bei dem es Sinn macht, keinen Standardkonstruktor zu haben.
-
Kellerautomat schrieb:
volkard schrieb:
Nee. Serialisierter, die Standdardkonstruktoren verlangen, sind für mich indiskutabel.
Gib mal ein Beispiel von einem Typen, bei dem es Sinn macht, keinen Standardkonstruktor zu haben.
Umgekehrt! Wo braucht man ihn? Was man nicht braucht, soll man nicht in die Schnittstelle tun.
Also mal alle Monstertypen im Rollenspiel sollen keinen haben. Wäre doch totaler Unfug. Streams sollen keinen haben. Ich könnte auch damit leben, daß Zahlen und Strings keinen haben. Manche rohe Typen dürften, um andere Meckerstellen der Sprache zu umgehen, sowas erlauben wieint i=uninitialized;//neues schlüsselwort. ein =0 wäre zu lahm und //die magic number wäre inhaltlich äußerst fragwürdig. do i=rand()%100;//meckerstelle: i kann hier nicht erzeigt werden, weil… while(schonGezogen(i));//…i hier gebraucht wird return i;
.
Zusammen mit dem RAII-Dingens mache ich eigentlich meine Variablen so lokal, daß ich sie stets erst anlege, wenn sie schon sinnvoll initialisiert werden können.
Ok, man braucht dafür, um kein schlechtes Gewissen zu bekommen, auch mal emplace_back und einen vector mit fixer Maximalgröße, der nicht im Freispeicher wohnt.
-
volkard schrieb:
Umgekehrt! Wo braucht man ihn? Was man nicht braucht, soll man nicht in die Schnittstelle tun.
Alles, was irgendwie in Container soll. Alles, was irgendwie Movable ist bzw. einen "empty" Zustand hat.
volkard schrieb:
Also mal alle Monstertypen im Rollenspiel sollen keinen haben. Wäre doch totaler Unfug.
Noe, warum. Wenn du ein Monster default-Konstrutierst, haste sowas wie ein moved-from Monster. Das kriegst du spaetestens wenn du einen Move-Konstrutkor / operator = verwendest. Also kannst du auch gleich einen Defaultkonstruktor bereitstellen.
volkard schrieb:
Streams sollen keinen haben.
Genau wie mit den Monstern.
volkard schrieb:
Ich könnte auch damit leben, daß Zahlen und Strings keinen haben. Manche rohe Typen dürften, um andere Meckerstellen der Sprache zu umgehen, sowas erlauben wie
int i=uninitialized;//neues schlüsselwort. ein =0 wäre zu lahm und //die magic number wäre inhaltlich äußerst fragwürdig. do i=rand()%100;//meckerstelle: i kann hier nicht erzeigt werden, weil… while(schonGezogen(i));//…i hier gebraucht wird return i;
.
Zusammen mit dem RAII-Dingens mache ich eigentlich meine Variablen so lokal, daß ich sie stets erst anlege, wenn sie schon sinnvoll initialisiert werden können.
Ok, man braucht dafür, um kein schlechtes Gewissen zu bekommen, auch mal emplace_back und einen vector mit fixer Maximalgröße, der nicht im Freispeicher wohnt.uninitialized finde ich auch gut. Hat aber mit Defaultkonstruktoren wenig zu tun.
-
Alle Deine Argument für Standardkonstruktoren sind also nicht inhaltlicher Art im Sinne der Anwendungslogik, sondern programmiertechnische Zugeständnisse.
Kellerautomat schrieb:
volkard schrieb:
Umgekehrt! Wo braucht man ihn? Was man nicht braucht, soll man nicht in die Schnittstelle tun.
Alles, was irgendwie in Container soll. Alles, was irgendwie Movable ist bzw. einen "empty" Zustand hat.
Ja, auch ein Punkt, den ich doof finde: Objekte wegmoven, ohne daß der Bezeichner verschwindet. Ich hätte gerne
ding.foo(); aufruf(move(ding)); //ding.foo();//gäbe compilerfehler
aufruf müßte die unbedingte Destuktionsverantwortung haben. Normalerweise wird an einen Subaufruf weitergemovt. Im Falle eines vorhergehenden return/throw muss ding halt wie eigene lokale Variablen destruiert werden. Für solche Sachen werfe ich Augen auf Rust und Konsorten.
Kellerautomat schrieb:
uninitialized finde ich auch gut. Hat aber mit Defaultkonstruktoren wenig zu tun.
Bei ints auf dem Stack schon.