C zu C++ - Einfacherer Übergang?
-
ja schrieb:
RAII stinkt schrieb:
Zu Mutexen: Mutex-Freigabe sollte so früh wie möglich erfolgen und nicht erst am Ende des Scopes. Man kann sich natürlich so behelfen:
void f() { { Mutex lock; do_something_that_is_not_thread_safe(); } // <-- unlock! do_other_stuff(); do_more_stuff(); }
Aber das ist häßlicher und unituitiver als eine manuelle Freigabe mittels einer simplen Unlock-Funktion.
RAII ist typisch für C++ insgeamt: man erfindet ein, auf den ersten Blick, tolles Feature, baut es sofort in die Sprache ein und schaltet dann sein Gehirn aus.
Ja, man kann sein Hirn ausschalten und es so machen, oder einfach in do_something_that_is_not_thread_safe() locken.
Vorausgesetzt Du hast Zugriff auf die Interna dieser Funktion. Und selbst wenn, was machst Du, wenn ein Lock in do_something_that_is_not_thread_safe() nicht immer erwünscht ist, oder in den Lock von f() noch anderer Code hinein muß? RAII ist keine generelle Lösung für Resource Allocation (was leider immer wieder propagiert wird), sondern ein halbdurchdachte Krücke, die schlechten Code erst möglich macht
-
RAII really stinks! schrieb:
Vorausgesetzt Du hast Zugriff auf die Interna dieser Funktion. Und selbst wenn, was machst Du, wenn ein Lock in do_something_that_is_not_thread_safe() nicht immer erwünscht ist, oder in den Lock von f() noch anderer Code hinein muß?
Wenn du willst, kannst du immer noch das Zeugs das gelockt werden muss in ne 2. Funktion stecken. Und was ist an nem Scope mit Klammer so schlimm? Mit try{}finally{} machst du auch immer nen scope auf und zusätzlich hast du ne Menge Code duplication, try - lock - finally - unlock immer und immer wieder.
Dein einziges Argument war bis jetzt, dass du es unleserlich findest und so ein Schwachsinn, dass man wegen RAII nicht merken würde, wenn man ein File öffnet.
RAII ist keine generelle Lösung für Resource Allocation (was leider immer wieder propagiert wird), sondern ein halbdurchdachte Krücke, die schlechten Code erst möglich macht
Na, dann zeig mal was das mit RAII nicht geht.
-
~john schrieb:
Java krankt fast an dem gleichen Problem, Nullpointer Exceptions sind bei Java allgegenwertig, also muß man auch mit Java aufpassen, daß Zeiger (Referenzen sind ganz banale Zeiger, wie man sie von Pascal kennt) nicht in Nirwana weisen. Java kennt lediglich keine Pointerarithmetik, auf die man in vielen Sprachen ohne GC ebenfalls verzichtet hat: Pascal, Modula-II, Ada, Fortran, …
Und wieder ist ein C/C++ Thread zu einem C++ ist misst, Java ist besser ausgeartet. Ich wollte nur klarstellen, dass in Java es keine Pointer gibt, es gibt Referenzen, und diese können nicht ins Nirwana zeigen. Sie können entweder Null sein oder ein Objekt referenzieren.
Zu RAII mal eine Frage. Wie könnt ihr im Dtor externe Ressourcen freigeben? Z.B. wenn man ein File-Stream schließt, dann kann eine Ausnahme fliegen. Deswegen ist es ein wenig komplizierter (siehe Beispiel und Link). Der Dtor wirft also die Exception und dann stürzt das gesamte Programm ab.
String file; FileInputStream stream = null; try { stream = new FileInputStream(file); stream.read(); } catch (FileNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } finally { if (stream != null) { try { stream.close(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
http://tutorials.jenkov.com/java-exception-handling/exception-handling-templates.html
na schrieb:
RAII really stinks! schrieb:
Vorausgesetzt Du hast Zugriff auf die Interna dieser Funktion. Und selbst wenn, was machst Du, wenn ein Lock in do_something_that_is_not_thread_safe() nicht immer erwünscht ist, oder in den Lock von f() noch anderer Code hinein muß?
Wenn du willst, kannst du immer noch das Zeugs das gelockt werden muss in ne 2. Funktion stecken. Und was ist an nem Scope mit Klammer so schlimm? Mit try{}finally{} machst du auch immer nen scope auf und zusätzlich hast du ne Menge Code duplication, try - lock - finally - unlock immer und immer wieder.
Dein einziges Argument war bis jetzt, dass du es unleserlich findest und so ein Schwachsinn, dass man wegen RAII nicht merken würde, wenn man ein File öffnet.
RAII ist keine generelle Lösung für Resource Allocation (was leider immer wieder propagiert wird), sondern ein halbdurchdachte Krücke, die schlechten Code erst möglich macht
Na, dann zeig mal was das mit RAII nicht geht.
Bei dem leerem Block weiß man aber nicht was er macht. Ist der Mutex für RAII konzipiert? Was, wenn man einen anderen Mutex nimmt? Bei einem mutex.unlock() weiß man sofort was Sache ist und der Compiler meckert, wenn man einen Mutex nimmt, der sich anders unlock-en lässt.
-
DEvent schrieb:
Z.B. wenn man ein File-Stream schließt, dann kann eine Ausnahme fliegen. ...
String file; FileInputStream stream = null; try { stream = new FileInputStream(file); stream.read(); } catch (FileNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } finally { if (stream != null) { try { stream.close(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
Das hat mich schon immer interessiert, warum beim stream.close() eine Exception auftreten kann. Wie kann sowas passieren? Ist dann das File für immer offen, wenn nicht mal mehr das OS es schließen kann? Und was soll man in so einem Fall machen, außer e.printStackTrace()?
-
@DEvent
Warum sollte man in einem Dtor keine Exception fangen können?~foo() try { close(fh); } catch(...) { print_backtrace(); }
Hat vorallem den Vorteil, dass man den Code nur einmal zentral schreiben muss und nicht überall wo man eine Datei aufmacht.
-
DEvent schrieb:
Zu RAII mal eine Frage. Wie könnt ihr im Dtor externe Ressourcen freigeben? Z.B. wenn man ein File-Stream schließt, dann kann eine Ausnahme fliegen. Deswegen ist es ein wenig komplizierter (siehe Beispiel und Link). Der Dtor wirft also die Exception und dann stürzt das gesamte Programm ab.
Falls man etwas benutzt das beim Ressourcen freigeben eine Exception wirft ist man doch gerne eingeladen das ganze mit einem try {something();} catch (std::exception) {} abzufangen. Wenn etwas bei der Resourcenfreigabe scheitert kann man sowieso nicht mehr viel machen ausser es zu ignorieren. Auch wenn jetzt einige Idioten behaupten wollen ein catch-Block dürfe niemals leer sein, die sollen mir erklären was man machen soll wenn man beim Schließen eines File-Streams eine Exception kriegt. Bill Gates anrufen
?
-
DEvent schrieb:
...
RAII ist keine generelle Lösung für Resource Allocation (was leider immer wieder propagiert wird), sondern ein halbdurchdachte Krücke, die schlechten Code erst möglich macht
Na, dann zeig mal was das mit RAII nicht geht.
Bei dem leerem Block weiß man aber nicht was er macht. Ist der Mutex für RAII konzipiert? Was, wenn man einen anderen Mutex nimmt? Bei einem mutex.unlock() weiß man sofort was Sache ist und der Compiler meckert, wenn man einen Mutex nimmt, der sich anders unlock-en lässt.
Was soll das bedeuten? Das beispiel von "RAII stinkt" war sowieso falsch. Du verwendest beim Mutex einen Scoped-Lock der den Mutex lockt und unlockt, und nich der Mutex selber macht im Destruktor das unlock, was ja Quatsch wäre. IM Scoped-Lock kannst du auch was anderes als unlock im Destruktor implementieren.
-
DEvent schrieb:
String file; FileInputStream stream = null; try { stream = new FileInputStream(file); stream.read(); } catch (FileNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } finally { if (stream != null) { try { stream.close(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
in C++ sieht es so aus:
String file; FileInputStream stream(file); stream.read();
maximal mit einem try catch drumherum um filenotfound zu fangen:
try { String file; FileInputStream stream(file); stream.read(); } catch(FileNotFound& e) { ... }
da sieht RAII ja furchtbar aus, oder?
-
DEvent schrieb:
Und wieder ist ein C/C++ Thread zu einem C++ ist misst, Java ist besser ausgeartet.
Was erwartest Du, wenn es immer die gleichen Einwürfe sind, bei Themen wo sie gar nicht hingehören?
DEvent schrieb:
Ich wollte nur klarstellen, dass in Java es keine Pointer gibt, es gibt Referenzen, und diese können nicht ins Nirwana zeigen. Sie können entweder Null sein oder ein Objekt referenzieren.
Und das stimmt zu 100% mit den Zeigern aus Oberon-2 überein, und das ist älter als Java. Das geradezu starrköpfige Beharren der meisten Java-Nutzer Java hätte keine Zeiger ist ziemlich albern.
String file; FileInputStream stream = null; try { stream = new FileInputStream(file); stream.read(); } catch (FileNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } finally { if (stream != null) { try { stream.close(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
Offensichtlich Bist Du der Meinung, daß Du das Problem innerhalb des finally-Blocks lösen kannst. Wenn dem so ist, läßt sich das Problem analog in einem Destruktor lösen. Da besteht keinerlei Unterschied.
Unterschiede gibt es zwischen C++ und Java, wenn der finally-Block bzw. der Destruktor selbst Exceptions werfen darf. Dann kann beim Abarbeiten der Blöcke eine weitere Exeception auftreten. Chained Exceptions kennt C++ nicht, so daß man hier ganz schnell bei unexpected() landet. Ergo, ist es in C++ am besten Destruktoren grundsetzlich keine Exceptions werfen zu lassen. Eine globale Error-Queue löst das Problem meist zuverlässig.
Und wenn man sich den vom Dir verlinkten Blog-Eintrag durchliest, weiß man warum finally reiner Code-Bloat ist, und dem Blog-Autoren ist das auch bewußt, sonst hätte er kaum diesen Eintrag verfaßt. Ergo tritt genau das auf, was seit Jahren aus der C++ Entwicklerlager an Java kritisiert wird.
DEvent schrieb:
Ist der Mutex für RAII konzipiert?
Man muß schon die Klassen kennen, die man verwendet. Der Rest ist Gewöhnung. So wie Du mit dem Gebrauch von finally vertraut bist, ist es ein C++ Programmierer mit RAII.
-
OldManJava schrieb:
Das hat mich schon immer interessiert, warum beim stream.close() eine Exception auftreten kann. Wie kann sowas passieren? Ist dann das File für immer offen, wenn nicht mal mehr das OS es schließen kann? Und was soll man in so einem Fall machen, außer e.printStackTrace()?
weil evtl. erst beim schließen die daten geschrieben werden und dementsprechend auch erst dann ein fehler auftreten kann.
Den Fall müsste man also genauso behandeln wie andere Fehler.
-
DrGreenthumb schrieb:
OldManJava schrieb:
Das hat mich schon immer interessiert, warum beim stream.close() eine Exception auftreten kann. Wie kann sowas passieren? Ist dann das File für immer offen, wenn nicht mal mehr das OS es schließen kann? Und was soll man in so einem Fall machen, außer e.printStackTrace()?
weil evtl. erst beim schließen die daten geschrieben werden und dementsprechend auch erst dann ein fehler auftreten kann.
Den Fall müsste man also genauso behandeln wie andere Fehler.
In C++ indem man es ignoriert (oder in einer Log Datei schreibt).
http://www.parashift.com/c++-faq-lite/exceptions.html#faq-17.3Write a message to a log-file. Or call Aunt Tilda. But do not throw an exception!
~john schrieb:
Offensichtlich Bist Du der Meinung, daß Du das Problem innerhalb des finally-Blocks lösen kannst. Wenn dem so ist, läßt sich das Problem analog in einem Destruktor lösen. Da besteht keinerlei Unterschied.
Nö, da steht "TODO: Auto generated catch block". Es ist also auf meiner "TODO-Liste".
Man kann einen neuen File Stream erstellen, der dann die Daten irgendwo anders hinschreibt. Oder man zeigt ein "Warning: Harddisk on fire" in einer GUI Anwendung. Das alles wollt ihr also in einen Dtor packen, in eine Klasse, die damit überhaupt nichts zu tun hat (MVC Model)? Oder irgendwie in einen global Zustand abspeichern, damit man irgendwie den Anwender informiert?
Die Exception kann man weiterleiten (re-throw) zu einem Objekt, der was damit anfangen kann. Dafür sind die Exceptions da und das Weiterleiten ist essentieller Bestandteil von Exceptions. Da Dtors keine Exceptions werfen dürfen, muss man das in C++ irgendwie anders lösen.
Es hat wohl seinen Grund wieso die Streams der STL nicht mit Exceptions arbeiteten und man eher wie in C programmiert:
ifstream is; is.open ("test.txt"); if ( (is.rdstate() & ifstream::failbit ) != 0 ) cerr << "Error opening 'test.txt'\n"; return 0;
-
Du kannst C++ fstreams auch so einstellen, dass sie Exceptions werfen und eine close Funktion gibt es auch.
-
DEvent schrieb:
In C++ indem man es ignoriert (oder in einer Log Datei schreibt).
http://www.parashift.com/c++-faq-lite/exceptions.html#faq-17.3Write a message to a log-file. Or call Aunt Tilda. But do not throw an exception!
Was machst du in Java? Realistisch betrachtet.
Denken wir uns was triviales aus. Wir haben 2 Dateien und kopieren von der einen in die andere Datei:
FileInputStream src=null; FileInputStream trg=null; try { src=new FileInputStream(a); trg=new FileInputStream(b); copy(trg, src); } catch(...) { //whatever } finally { try { if(src!=null) { src.close(); } } catch(Exception e) { //whatever } finally { try { if(trg!=null) { trg.close(); } } catch(Exception e) { //whatever } } }
Die beiden whatevers sind nun das Problem. Dass man bei dem Code augenkrebs bekommt und einem erstmal richtig schlecht wird, davon abgesehen.
Wir muessen den Fehler jetzt irgendwie behandeln, aber es koennen ja auch 2 Fehler sein. Mehr als loggen ist da nicht wirklich drinnen. Denn wie koennen wir das nun an den caller uebergeben, dass wir einen fehler beim schliessen hatten? Bedenke dass wir uU nur 1 Datei jemals geoeffnet haben.
FileInputStream src=null; FileInputStream trg=null; ExceptionList el = new ExceptionList(); try { src=new FileInputStream(a); trg=new FileInputStream(b); copy(trg, src); } catch(Exception e) { el.add(e); throw el; } finally { try { if(src!=null) { src.close(); } } catch(Exception e) { el.add(e); throw el; } finally { try { if(trg!=null) { trg.close(); } } catch(Exception e) { el.add(e); throw el; } } }
So kann man natuerlich auch mehrere Exception werfen, nur wird dann das fangen bloed
Effektiv gesehen ist jede Form von exceptions aus destruktoren ein hack. c++ verbietet es einfach um eine menge probleme zu umgehen. C++ bietet aber genug moeglichkeiten um dennoch den Fehler anzuzeigen - uU kann man ja eben doch eine exception werfen wenn man denn so will. oder signale verwenden oder sonstwas.
aber egal was man macht: es ist genau wie in jeder anderen Sprache ein hack.
Es hat wohl seinen Grund wieso die Streams der STL nicht mit Exceptions arbeiteten
Ja. Und soll ich ihn dir verraten?
Weil man keine exceptions erzwingen wollte. Alle funktionen in der C++ Library sind exception neutral (von wenigen Ausnahmen). Man wollte, gerade am Anfang der standardisierung nicht exceptions erzwingen als der support dafuer einfach nicht da war.deshalb kann man exceptions aktivieren und deaktivieren wie man es braucht.
PS:
ich hatte nen fehler im aufraeumcode. und das bei soetwas trivialem. haendisch resourcen aufraeumen ist einfach eine katastrophe...
-
Shade Of Mine schrieb:
deshalb kann man exceptions aktivieren und deaktivieren wie man es braucht.
Das blöde ist nur, dass man ohne Exceptions keine Möglichkeit hat, einen Konstruktor fehlschlagen zu lassen.
Und auch wenn man keine Exceptions verwenden will, zwingt einen allein die Tatsache, dass es in C++ Exceptions gibt, trotzdem dazu, ständig darauf zu achten dass der Code den man schreibt Exception-safe ist. Man verschwendet also Zeit, nur um sich um Sprachfeatures zu kümmern, die man gar nicht nutzen will. Im Prinzip das selbe wie die Kopierkonstruktoren und Zuweisungsoperatoren, für die man ständig Zeit opfert obwohl man sie eigentlich gar nicht braucht.
-
namespace invader schrieb:
Und auch wenn man keine Exceptions verwenden will, zwingt einen allein die Tatsache, dass es in C++ Exceptions gibt, trotzdem dazu, ständig darauf zu achten dass der Code den man schreibt Exception-safe ist.
Die Standardbibliothek ist ziemlich defensiv, was Exceptions anbelangt. Wenn du selbst keine Exceptions und keine Bibliotheken mit Exceptions verwendest, kann also nicht viel passieren. Abgesehen davon ist Exceptionsicherheit fast gratis, wenn man RAII verwendet. Man muss ja nicht C in C++ programmieren.
Ich weiss zwar nicht, wie oft das bereits erwähnt wurde, aber in anderen Programmiersprachen hat man das Problem ebenfalls. Nur Speicher wird einem aufgeräumt, andere Ressourcen muss man selbst sichern. Und gerade in Sprachen wie Java, die an jeder Ecke eine Exception werfen und mit
finally
zu manueller Freigabe zwingen, ist das nicht besonders lustig.namespace invader schrieb:
Man verschwendet also Zeit, nur um sich um Sprachfeatures zu kümmern, die man gar nicht nutzen will. Im Prinzip das selbe wie die Kopierkonstruktoren und Zuweisungsoperatoren, für die man ständig Zeit opfert obwohl man sie eigentlich gar nicht braucht.
Schön, dass du gerade ein Beispiel bringst, auf das das Gegenteil zutrifft. Kopierkonstruktoren und Zuweisungsoperatoren musst du in den wenigsten Fällen selbst implementieren – nämlich dann, wenn du eine spezifische Kopiersemantik möchtest. In den meisten Fällen tun aber die eingebauten Funktionen das Richtige.
-
@namespace invader
Wenn man keine Exceptions nutzen will, dann nutzt man einfach keine Exceptions und muss sich darüber überhaupt keine Gedanken machen. Wenn man Konstruktoren fehlschlagen lassen will, dann muss man eben einen "ungültig" Status für die Objekte bauen. Ist ja keine wilde Sache. In anderen Sprachen mit Exceptions muss man sich auch um Exceptions sorgen machen, auch wenn man die nicht nutzen will@DEvent
Man kann auch in C++ explizit eine Datei schließen, wenn man den Fehlercode behandeln möchte. Die STL kennt übrigens keine Streams
-
Nexus schrieb:
Die Standardbibliothek ist ziemlich defensiv, was Exceptions anbelangt. Wenn du selbst keine Exceptions und keine Bibliotheken mit Exceptions verwendest, kann also nicht viel passieren.
Aber wehe, wenn dann irgendwann mal an dem Code, den ich Aufrufe was verändert wird, so dass er doch Exceptions wirft.
Nicht Exception-sicherer C++-Code ist unschön und problematisch wiederzuverwenden, zumal sich auch nicht so auf den ersten Blick erkennen lässt, wo er nicht exceptionsicher ist. Man sollte also vernünftigerweise schon auf Exceptionsicherheit achten, was ärgerlich ist wenn man gar keine Exceptions verwenden will.
Abgesehen davon ist Exceptionsicherheit fast gratis, wenn man RAII verwendet.
Eben nur _fast_ gratis, man muss sie trotzdem immer im Hinterkopf haben.
Gerade wenn man C-Bibliotheken verwendet bedeutet das, dass man einen Haufen ansonsten sinnloser Wrapper-Klassen schreiben muss.
Kopierkonstruktoren und Zuweisungsoperatoren musst du in den wenigsten Fällen selbst implementieren – nämlich dann, wenn du eine spezifische Kopiersemantik möchtest.
Man muss sich aber zu jeder Klasse Gedanken machen, ob die notwendig ist. Und vor allem auch dann, wenn man gar nicht vorhat, die Objekte zu kopieren, z.B. weil es sinnlos wäre. Aber jemand anders der den Code irgendwann wiederverwendet könnte es ja tun wollen.
rüdiger schrieb:
Wenn man Konstruktoren fehlschlagen lassen will, dann muss man eben einen "ungültig" Status für die Objekte bauen.
Fällt bei mir in die Kategorie "hässlicher Hack". Warum muss ich dass für jede Klasse aufs Neue selbst implementieren?
In anderen Sprachen mit Exceptions muss man sich auch um Exceptions sorgen machen, auch wenn man die nicht nutzen will
In anderen Sprachen, in denen Exceptions grundlegender Bestandteil sind und von allen Bibliotheken konsistent verwendet werden, werde ich aber Exceptions niemals nicht nutzen wollen, somit tritt das Problem nicht auf.
Das was ich an C++ so abscheulich finde ist, dass Probleme an allen möglichen Sprachfeatures damit gerechtfertig werden, dass man sie ja nicht nutzen muss wenn man sie nicht braucht/will. Aber das hat zur Folge, dass man hässlichen, schlecht wiederverwendbaren Code schreibt.
IMHO kann man in C++ nur schönen, perfekten Code schreiben, wenn man sich auf alle Sprachfeatures einlässt und (gerade wenn man viele verschiedene Bibliotheken und Frameworks verwendet) viel Zeit investiert. Wenn man produktiv sein will, muss man sich auf Kompromisse einlassen (Was deswegen bei 95% des C++-codes, der so geschrieben wird, auch gemacht wird).
Natürlich kann man das für alle Sprachen irgendwie sagen, aber IMHO ist es bei C++ am schlimmsten; schönen, wiederverwendbaren Code zu schreiben und produktiv das zu machen was man eigentlich vorhat sind meinem Eindruck nach bei C++ entgegengesetze Ziele. Eine gute Sprache sollte es erleichtern, beides gleichzeitig zu machen.
-
namespace invader schrieb:
Nicht Exception-sicherer C++-Code ist unschön und problematisch wiederzuverwenden, zumal sich auch nicht so auf den ersten Blick erkennen lässt, wo er nicht exceptionsicher ist. Man sollte also vernünftigerweise schon auf Exceptionsicherheit achten, was ärgerlich ist wenn man gar keine Exceptions verwenden will.
Das macht keinen Sinn.
Wenn du in C++ ohne exceptions arbeitest, dann brauchst du dich nicht um exceptions kuemmern, da die standard library keine wirft.
wenn man an die zukunft denkt und vermutet dass der code vielleicht doch irgendwannmal exceptions verwenden wird, tja, dann hast du das selbe problem wie in jeder sprache. aber c++ zwingt dich zu nichts. es gibt sogar systeme wo exceptions technisch garnicht unterstuetzt werden und trotzdem kann man c++ programmieren.
Gerade wenn man C-Bibliotheken verwendet bedeutet das, dass man einen Haufen ansonsten sinnloser Wrapper-Klassen schreiben muss.
nein, muss man nicht.
es macht vielleicht sinn, aber man kommt mit einer einzigen klasse aus: ein scopeguard. mehr ist nicht notwendig.Man muss sich aber zu jeder Klasse Gedanken machen, ob die notwendig ist. Und vor allem auch dann, wenn man gar nicht vorhat, die Objekte zu kopieren, z.B. weil es sinnlos wäre. Aber jemand anders der den Code irgendwann wiederverwendet könnte es ja tun wollen.
man muss beim programmieren denken? O_o
selbes problem hast du in allen sprachen.
rüdiger schrieb:
Wenn man Konstruktoren fehlschlagen lassen will, dann muss man eben einen "ungültig" Status für die Objekte bauen.
Fällt bei mir in die Kategorie "hässlicher Hack". Warum muss ich dass für jede Klasse aufs Neue selbst implementieren?
ist in allen sprachen so.
In anderen Sprachen mit Exceptions muss man sich auch um Exceptions sorgen machen, auch wenn man die nicht nutzen will
In anderen Sprachen, in denen Exceptions grundlegender Bestandteil sind und von allen Bibliotheken konsistent verwendet werden, werde ich aber Exceptions niemals nicht nutzen wollen, somit tritt das Problem nicht auf.
irrelevant. c++ bietet dir optionen und moeglichkeiten.
wenn du keine exceptions willst: bitte, c++ bietet dir die moeglichkeit
du willst exceptions nutzen: bitte, c++ bietet dir die moeglichkeitjava sagt: du musst exceptions nutzen. was in java kein problem, in c++ waere es aber eins, da c++ auch auf systemen laufen muss, wo es keine vm gibt und wo man die kosten von exceptions an die infrastruktur nicht zahlen will/kann.
Das was ich an C++ so abscheulich finde ist, dass Probleme an allen möglichen Sprachfeatures damit gerechtfertig werden, dass man sie ja nicht nutzen muss wenn man sie nicht braucht/will. Aber das hat zur Folge, dass man hässlichen, schlecht wiederverwendbaren Code schreibt.
bloedsinn.
wenn du features nicht nutzt, macht das garnichts. natuerlich ist der code ohne feature A nicht so schoen wie mit feature A. ist ja der sinn. wozu braeuchte ich sonst das feature A?
man kann aber ohne probleme grosse anwendungen in c++ schreiben ohne exceptions und templates zu nutzen. gibt massig beispiele dafuer. natuerlich waere der code mit exceptions und templates vermutlich schoener...
selbes gilt uebrigens fuer jede sprache. wenn du keine generics in java nutzen willst, dann kannst du das. aber dein code wird nicht so schoen werden wie wenn du generics nutzen wuerdest.
IMHO kann man in C++ nur schönen, perfekten Code schreiben, wenn man sich auf alle Sprachfeatures einlässt und (gerade wenn man viele verschiedene Bibliotheken und Frameworks verwendet) viel Zeit investiert. Wenn man produktiv sein will, muss man sich auf Kompromisse einlassen (Was deswegen bei 95% des C++-codes, der so geschrieben wird, auch gemacht wird).
wie in jeder sprache.
Natürlich kann man das für alle Sprachen irgendwie sagen, aber IMHO ist es bei C++ am schlimmsten; schönen, wiederverwendbaren Code zu schreiben und produktiv das zu machen was man eigentlich vorhat sind meinem Eindruck nach bei C++ entgegengesetze Ziele. Eine gute Sprache sollte es erleichtern, beides gleichzeitig zu machen.
willkommen in der realen welt.
die perfekte sprache gibt es nicht.guter code und schnell code schreiben widerspricht sich. guter code muss ueberlegt sein, egal ob in python, c++ oder cobol. c++ bietet dir alle tools die du brauchst um guten code zu schreiben. natuerlich musst du aber auch noch immer selber den code schreiben.
php ist was das betrifft zB sehr schlecht dran. code in php ist fast immer eine katastrophe, aber enorm schnell geschrieben. python ist vielleicht noch am besten dran... aber die sprache die du hier suchst, die gibt es nicht. und c++ ist von deinen zielen nicht weiter weg als java oder c#.
es gibt zB eine interessante library genannt mef (Managed Extensibility Framework) die genau zu diesem zweck existiert. code reuse ist schwer. in jeder sprache. mef erlaubt jetzt code reuse auf binary ebene in .net - eben weil guter code und realer code 2 unterschiedliche welten sind.
-
namespace invader schrieb:
Aber wehe, wenn dann irgendwann mal an dem Code, den ich Aufrufe was verändert wird, so dass er doch Exceptions wirft.
Nicht Exception-sicherer C++-Code ist unschön und problematisch wiederzuverwenden, zumal sich auch nicht so auf den ersten Blick erkennen lässt, wo er nicht exceptionsicher ist. Man sollte also vernünftigerweise schon auf Exceptionsicherheit achten, was ärgerlich ist wenn man gar keine Exceptions verwenden will.
Es kommt natürlich drauf an, welchen Level von Exceptionsicherheit man anstrebt. Will man nur die minimale Garantie - also dass alle Ressourcen wieder freigegeben werden ohne die Garantie dass der State gleich bleibt, dann macht es keinen Unterschied, ob du mit oder ohne Exceptions arbeitest. Einfach RAII befolgen. Verwendest du dabei nur das was die STL bietet, erfüllst du automatisch diese Anforderungen und musst überhaupt nicht nachdenken, das was dir der Compiler an copy-ctor, dtor und op= generiert reicht völlig aus.
Richtig ist, dass man für höhere Garantieren natürlich immer etwas Hirnschmalz reinstecken muss, zumindest für copy&swap. Aber das sind höhere Garantien. Wenn du nicht erwartest dass in deinem Code überhaupt behebbare Exceptions auftreten können, dann brauchst du darüber nicht nachdenken. Wenn ein new-fehlschlägt dann ist eh alles verloren, da bringt dir die höhere Garantie nichts.
Man muss sich aber zu jeder Klasse Gedanken machen, ob die notwendig ist. Und vor allem auch dann, wenn man gar nicht vorhat, die Objekte zu kopieren, z.B. weil es sinnlos wäre. Aber jemand anders der den Code irgendwann wiederverwendet könnte es ja tun wollen.
Der Punkt ist, dass dies für einen Großteil aller Klassen nicht notwendig ist. Der Normalfall ist nicht speziell. Und ob der Normalfall zutrifft merkt man spätestens, wnen man den dtor implementieren muss. Erst wenn dort Magie passiert, braucht man sich Gedanken drüber zu machen, ob die default-Semantik so sinnvoll ist. Aber wie bereits gesagt wurde, ist das ein Problem für alle Sprachen.
Wenn jemand kopieren will und du dir keine Gedanken drüber machtest obwohl du es hättest tun sollen, bist du eben aufgeschmissen. Wenn jemand den Code umschreibt sodass er Exceptions verwendet und du nicht drüber nachdenkst, bist du aufgeschmissen. Wenn jemand deinen Code in einem anderen Kontext verwendest und du nicht drüber nachdenkst bist du aufgeschmissen. Wenn du beim Programmieren nicht nachdenkst, bist du aufgeschmissen.Natürlich ist die Frage, ob es sich lohnt. Wenn man den Code nicht dafür auslegt dass er Exceptions vertragen soll, dann ist das okay, solange man den Kontext unter Kontrolle hat. Wenn man aber davon ausgeht, dass der Code in völlig fremder Umgebung zum Einsatz kommen wird, dann muss man natürlich Arbeit reinstecken. Aber das ist das Problem der Widerverwendbarkeit für alle Sprachen(mal von funktionalen Sprachen abgesehen. Wo kein State existiert kann nichts schief gehen).
Das was ich an C++ so abscheulich finde ist, dass Probleme an allen möglichen Sprachfeatures damit gerechtfertig werden, dass man sie ja nicht nutzen muss wenn man sie nicht braucht/will. Aber das hat zur Folge, dass man hässlichen, schlecht wiederverwendbaren Code schreibt.
C++ lässt einem die Wahl, die Features zu verwenden, natürlich muss man nicht. Du hast natürlich recht, dass bestimmte Features Probleme machen, insbesondere wenn andere Features hinzu kommen (wie exceptions und dtor). Aber das sind konzeptionelle Probleme die man nicht lösen kann. Mehrere Exceptions gleichzeitig werfen zu wollen ist nunmal in jeder Sprache problematisch. Normalerweise ist das kein Problem, außer im aufräumcode. Aber ob du in einem finallyblock das Problem hast, dass eine Exception geworfen werden kann oder in einem Destruktor macht keinen großen Unterschied.
IMHO kann man in C++ nur schönen, perfekten Code schreiben, wenn man sich auf alle Sprachfeatures einlässt und (gerade wenn man viele verschiedene Bibliotheken und Frameworks verwendet) viel Zeit investiert. Wenn man produktiv sein will, muss man sich auf Kompromisse einlassen (Was deswegen bei 95% des C++-codes, der so geschrieben wird, auch gemacht wird).
Aber auch das gilt für jedes Handwerk. Du musst dich mit deinen Werkzeugen auskennen. Wenn du das nicht tust, ist das Ergebnis minderwertig. Und wenn du im Team arbeitest und deine Teamkollegen mit den Werkzeugen nicht umgehen können, bringt es auch nicht viel, wenn du es kannst.
Wichtiger ist allerdings, dass "schöner, perfekter Code" und das Auslassen von Sprachfeatures nicht zusammen passen. Wenn ich perfekten Code ohne ein bestimmtes Feature schreiben kann, dann ist das Feature sinnlos und kann bedenkenlos vergessen werden. Wenn es mit dem Feature schöner geht, dann ist es sinnvoll und sollte verwendet werden. Der Handwerker der 1000 Werkzeuge kennt und nutzen kann, wird am Ende sicherlich auch schönere Ergebnisse haben als ein Handwerker der sich mit Hammer und Schraubenzieher begnügt. Und sicherlich hat der Handwerker mit den 1000 Werkzeugen viele Stunden länger geübt als der mit dem Miniwerkzeugkasten.Dieser Punkt wirkt auf mich so, als ob du kritisierst, dass man Zeit investieren muss, um irgendwo gut zu sein. Aber das gilt doch wirklich überall im Leben. Natürlich gibt es Sprachen, die schneller zu erlernen sind als C++, aber die haben auch einen viel kleineren Werkzeugkasten. Und es ist klar, dass man mit einem kleineren Werkzeugkasten zu Beginn schneller ans Ziel kommt, weil man eben nicht so viel Wissen muss. Trotzdem ist es manchmal besser, einen richtig großen Werkzeugkasten zu haben, der immer genau das Werkzeug enthält, das man gerade benötigt.
Dies bedeutet auch nicht, dass man mit dem großen Werkzeugkasten irgendwann schneller ist. Das behaupte ich nicht. Es ist eher so, dass man viel mehr Möglichkeiten und damit eine ganz andere Messlatte hat. Für einen kleinen Werkzeugkasten ist Perfektion einfach zu erreichen: mach was geht und wenn nichts mehr geht ist es perfekt. Nur was beim kleinen Werkzeugkasten das absolut perfekte Resultat ist, kann beim großen Werkzeugkasten wieder verbesserungswürdig sein, weil er die filigranen Kleinstwerkzeuge für die Feinarbeit enthält. Wo der eine nur 100er Schmirgelpapier hat, hat der andere das 1000er Nassschleifpapier und Feinpolitur mit denen er sich die nächsten Stunden noch vertreiben kann, bis alles glänzt und funkelt.
-
otze schrieb:
Wenn jemand kopieren will und du dir keine Gedanken drüber machtest obwohl du es hättest tun sollen, bist du eben aufgeschmissen. Wenn jemand den Code umschreibt sodass er Exceptions verwendet und du nicht drüber nachdenkst, bist du aufgeschmissen. Wenn jemand deinen Code in einem anderen Kontext verwendest und du nicht drüber nachdenkst bist du aufgeschmissen. Wenn du beim Programmieren nicht nachdenkst, bist du aufgeschmissen.
Nur dass C++ einen dazu zwingt, pausenlos über Dinge nachzudenken, die mit der Lösung des eigentlichen Problems nichts zu tun haben. Man vergeudet einen beachtlichen Teil seiner Zeit damit, alle Möglichen Dinge zu tun, die nur die Sprache (und nicht das Problem) von einem verlangt.
Das ist in anderen Sprachen nicht so. Einen finally-Block in Java (wobei ich gar nicht sagen will, dass Java nun übermäßig toll ist) schreibt man, weil das Problem es erfordert, dass bestimmte Dinge freigegeben werden, und nicht nur um es der Sprache rechtzumachen. Code zum Kopieren eines Objektes in C schreibt man nur, wenn das Problem es erfordert, dass so ein Objekt kopiert wird, und nicht um die Sprache daran zu hindern Unsinn anzustellen.
Wenn man aber davon ausgeht, dass der Code in völlig fremder Umgebung zum Einsatz kommen wird, dann muss man natürlich Arbeit reinstecken. Aber das ist das Problem der Widerverwendbarkeit für alle Sprachen
Aber gerade C++ verleitet dazu, nicht wiederverwendbaren code zu schreiben, der auch schlecht wieder "repariert" werden kann.
Natürlich ist es z.B. auch in C mühsam, auf alle Fehler richtig zu reagieren; wenn man schnell etwas fertig bekommen will schreibt man bei einem Fehler einfach eine Fehlermeldung nach stderr und beendet das Programm. Aber sowas sieht dann jeder, der über den Code guckt und kann es nachträglich sicher beheben. C++-Code im Nachhinein exceptionsicher zu machen ist mühsam und fehleranfällig.
Du musst dich mit deinen Werkzeugen auskennen. Wenn du das nicht tust, ist das Ergebnis minderwertig. Und wenn du im Team arbeitest und deine Teamkollegen mit den Werkzeugen nicht umgehen können, bringt es auch nicht viel, wenn du es kannst.
Wie gesagt ist der Grund dafür, dass in C++ minderwertige Ergebnisse erzeugt werden, vor allem der, dass es ansonsten schwierig ist produktiv zu ein. Dass sich aufgrund der hohen Komplexität von C++ ein Großteil der C++-Programmierer nicht wirklich mit C++ auskennen, kommt natürlich noch erschwerend hinzu.
Wichtiger ist allerdings, dass "schöner, perfekter Code" und das Auslassen von Sprachfeatures nicht zusammen passen. Wenn ich perfekten Code ohne ein bestimmtes Feature schreiben kann, dann ist das Feature sinnlos und kann bedenkenlos vergessen werden. Wenn es mit dem Feature schöner geht, dann ist es sinnvoll und sollte verwendet werden.
Das glaube ich nicht, und vermutlich sind der Grund für die ewige C++-Diskussion auch einfach unterschieche Auffassungen darüber, was genau "schöner, perfekter Code" ist.
Ein bestimmtes Problem (nach meinen Maßstäben) schön und elegant zu lösen erfordert nicht bestimmte, zur eleganten Lösung des Problems absolut erforderliche Sprachfeatures, sondern hängt vor allem davon ab, wie die zur Verfügung stehenden Möglichkeiten eingesetzt werden. Jedes Problem kann in (fast) jeder Sprache und mit jedem Paradigma elegant gelöst werden. Ein C-Programm für ein beliebiges, bestimmtes Problem kann genauso elegant sein wie ein C++-, Erlang- oder Haskellprogramm. Nur dass sich der Programmieraufwand dazu je nach Problem unterscheidet (und natürlich auch Laufzeit- und Speicheraufwand, Größe usw. des Erbebnisses)
Und IMHO ist der Programmieraufwand zur "eleganten" Lösung mit C++ in sehr vielen Fällen höher als in anderen Sprachen. Ohne Frage kann man in C++ auch sehr produktiv arbeiten, deshalb hat es sich so weit verbreitet, aber das was dabei in den meisten Fällen herauskommt empfinde ich nicht als schön.
Aber ich gebe ja zu, dass die Frage, was schöner, eleganter Code ist und wann man welchen schreiben sollte, subjektiv ist und auf eine Grundsatzdiskussion hinausläuft. Viele hier scheinen der Meinung zu sein, dass eine Sprache als Werkzeugkasten zu sehen ist, der idealerweise möglichst viel verschiedenen Kram enthält, von dem man sich das rauspicken kann was gerade irgendwie am besten passt. Ich will mir ja gar nicht anmaßen zu behaupten, dass deren subjektives Empfinden falsch ist (auch wenn sich viele Ärgerlichkeiten von C++ objektiv feststellen lassen). Aber nach meinem Empfinden entscheidet vor allem die Qualität und die gut durchdachte Zusammenstellung (und nicht sie Masse) der Sprachfeatures darüber, wie "gut" ein Sprache ist und wie einfach man damit eleganten Code schreiben kann, und demnach ist C++ eben eine unsaubere und relativ ekelhafte Sprache.