C++ VS Java ft. C#: To RAII or not to RAII



  • alternativlos schrieb:

    Das wäre eine notwendige Vorsichtsmassnahme. Hast du ein Beispiel, bei dem das auftritt?

    Notwendig nur wenn auch mehrere Threads involviert sind. Mich auch erstmal deine Begründung dafür interessieren, weshalb Shared Pointer auf nicht-Konstante Objekte eine schlechte Idee sind. ich lasse mich gerne überzeugen, aber einfach so hinnehmen kann ich die Aussage nicht, wenn ich nicht weiss warum.

    Ich sehe ein dass es Sinn macht das Objekt const zu machen, dass wenn alle nur lesen. Aber das ist nicht immer der Fall.



  • hm schrieb:

    Arcoth schrieb:

    lol

    Ist die Frage wirklich so unglaublich unbeantwortbar?
    [...]
    Es fiel mir nur auf, dass man sich um einige Dinge in (beispielsweise) Java weniger Gedanken machen muss, ohne einen Nachteil zu haben.

    In Java werden Dir Indirektionen aufgezwungen (fast alles sind Referenzen) und Du hast auch sonst kaum Kontrolle beim Speicherlayout. Speicherlayout ist heutzutage wichtiger denn je, denn so ein 2nd-Level-Cache-Miss kostet richtig viele Zyklen heutzutage. In C++ gibt es nur Indirektionen wenn du explizit danach fragst. Java Generics sind sehr schwach im Vergleich zu dem, was Dir C++ mit Templates bietet. Java Generics basieren auf Type Erasure und vom Compiler eingefügte Casts. C++ Templates funktionieren anders: "Monomorphisation". Dabei wird keine Typinformation weggeschmissen und es lässt sich alles besser optimieren. Wenn Du z.B. std::sort auf ein Array bestehend aus Dingen eines eigenen Typs mit einem benutzerdefinierten Vergleichsfunktor loslässt, dann wird eine spezielle std::sort-Funktion übersetzt, die genauso gut wie die handgeschriebene Version wäre. Deswegen spricht man im C++ Kontext auch von "zero cost abstraction", was 'ne ziemlich geniale Sache ist.

    Wenn Du 'ne Zeit lang mit C++ gearbeitet hast und so langsam ein Gefühl dafür bekommen hast, wie du die Sprachmittel effektiv einsetzen kannst, dann wirst Du die Programmiersprachenlandschaft mit anderen Augen sehen. So war's bei mir. Ich hab im Studium hauptsächlich Java programmiert und danach C++ angefangen, was jetzt auch schon wieder 8 Jahre her ist.

    Bad und ugly? Ja, gibt's natürlich auch. Es gibt viele Aspekte an C++, die ich scheiße finde, sowohl syntaktisch als auch semantisch. Der "Erfinder" Stroustrup sagte mal "Within C++, there is a much smaller and cleaner language struggling to get out.". Die Sprache verbessert sich in der Hinsicht von Version zu Version. Ich freu' mich schon auf "concepts lite" und "modules". Concepts lite werden diese "SFINAE-Template-Trickserei" ersetzen, die für Anfänger wahrscheinlich wie Hyroglyphen aussahen. Und mit "modules" werde ich hoffentlich diese lästigen Headerfiles los. Diese Redundanz zwischen Deklaration und Definition nervt mich einfach.

    Wer wissen will, wie 'ne Programmiersprache aussehen kann, die die guten Dinge von C++ behält, vereinfacht und mit Features aus der ML-Sprachfamilie (algebraische Datentypen, Pattern Matching) sowie mit einer eigenen Version von "region pointers" aus Cyclone vereint und dadurch sicherer und angenehmer wird, sollte sich mal Rust angucken. Als ich C++ gelernt hatte, fand ich dann Java eher blöd. Als ich Rust gelernt hatte, fand ich dann C++ eher blöd. 😃 SCNR!



  • Nathan schrieb:

    Man muss sich in C++ auch keine Gedanken um Speicherverwaltung machen, das ist ein Mythos! Destruktoren ermöglichen keinerlei Leaks jeglicher Art und sind wesentlicher besser als GC, der sich nur um Speicher kümmert.

    Du hast Recht, wenn man sich an das RAII Prinzip hält, dann deckt man vermutlich einen Großteil ab.
    Aber deckt man alle Fälle ab? Gibt es nicht trotzdem einige Fälle, in denen man doch ziemlich Hirnschmalz reinstecken muss? Exceptions, multithreading, sowas eventuell?

    Wie sieht's eigentlich aus mit der Frage ob man etwas auf dem Stack oder dem Heap anlegt? Da ich nie wirklich produktiv C++ programmiert habe, weiß ich gar nicht, wie man das abwägt?

    Allgemein sieht man hier im Forum halt immer wieder ziemlich gigantische Konstrukte, die irgendeiner mit viel Liebe zusammen gebastelt hat und wenn man die Sprache (C++) nicht wirklich verdammt gut kann, dann braucht man eine Weile, bis man das entwirrt hat und etwa versteht, wie genau es arbeitet.



  • hm schrieb:

    Es fiel mir nur auf, dass man sich um einige Dinge in (beispielsweise) Java weniger Gedanken machen muss, ohne einen Nachteil zu haben.

    Na dann musst du schon beispiele bringen.

    @Topic (achtung, eigene meinung):

    [Gut]
    -Schnell
    -Kurzer code
    -RAII
    -Lambdas!
    -Kompatibilität zu C

    [Schlecht]
    -Headersystem, keine module
    -Template fehlermeldungen (da keine concepts)
    -Keine reflection
    -Sehr überschaubare standardlib
    -Viele problematische hacks (0-terminierte strings, enable_if etc)
    -Alles mit präprozessor
    -Kompatibilität zu C



  • krümelkacker schrieb:

    [...]

    Das ist doch mal ein sehr interessanter Post, dankeschön 🙂

    Die Dinge klingen für mich theoretisch alle sehr nachvollziehbar, durch meine mangelnde Erfahrung im Beruf allerdings nicht wirklich konkret vorstellbar.

    Ist vermutlich auch Projekt-abhängig. Momentan eher im Webbereich mit Java und Spring unterwegs.
    Da ist nun mal nichts komplexes dabei. Man zieht seine MVC Struktur auf und die bisschen Logik, die es an diverse Stellen noch zu implementieren gibt ist auch nie so wirklich komplex.

    Eventuell krieg ich ja in meinem Berufsleben irgendwann mal die Möglichkeit, an einem richtigen C++ Projekt mitzuarbeiten. Vielleicht geht mir dann ein Lichtlein auf.
    Sofern hab ich halt eigentlich nur Bücher (Meyers) gelesen. In denen wird schnell klar, dass die Sprache wirklich elegant sein kann, aber so wirklich "das muss man haben!" Gefühl kam nie auf.

    Sowas wie die Annotationen in Java find ich zB auch sehr schön. Ist halt sehr abstrahiert. Weiß gar nicht, ob es ähnliches in C++ gibt.



  • hm schrieb:

    Gibt es nicht trotzdem einige Fälle, in denen man doch ziemlich Hirnschmalz reinstecken muss? Exceptions,

    Wenn Du dich an RAII hältst, dann ist alles automatisch Exception-safe.

    hm schrieb:

    multithreading

    Das kannst Du in Java genauso verkacken wie in C++. In Rust ist das schon viel schwieriger.

    hm schrieb:

    Wie sieht's eigentlich aus mit der Frage ob man etwas auf dem Stack oder dem Heap anlegt? Da ich nie wirklich produktiv C++ programmiert habe, weiß ich gar nicht, wie man das abwägt?

    Du verwendest einfach den kleinstmöglichen Gültigkeitsbereich auf'm Stack, falls möglich. Dank Move Semantics kann man einige Objekte auch effizient von A nach B umziehen lassen (z.B. von einer Fuktion in eine andere, jeweils im Stackframe, also bei der Parameterübergabe oder Rückgabe), ohne dass man da mit Heap-Allozierung und Pointern arbeiten muss. Wenn der Typ T "unhandlich" ist -- sizeof(T) groß oder T ist nicht effizient "move-bar" -- dann kann man eine Indirektion einbauen. Zum Beispiel über std::unique_ptr. Ich komme damit für fast alle Situationen aus. Das mag aber auch an meiner Anwendungsdomäne liegen. Es gibt sicherlich andere Fälle, wo es komplizierter wird.



  • Dann ist das ja alles weniger dramatisch, als gedacht.

    Zusätzlicher Denk-Aufwand kommt dann wahrscheinlich echt erst bei der Frage Heap <> Stack auf, hat aber bestimmt auch seine Vorteile, die Wahl zu haben.

    Du magst wohl Rust? 😃 Könnte man sicherlich auch mal ein Buch zu lesen. Aber dann hat man halt wieder nur ein Buch gelesen und nicht wirklich damit gearbeitet. Hm.



  • Was genau begeistert Euch eigentlich an C++ so sehr,

    Naja persönliche Begeisterung mag das eine sein ...

    Oftmals hat man aber Projektgegebenheiten zu berücksichtigen ...

    - Hat man 3d. Party libs in C/C++ ist der Aufwand eines Wrappers manchmal auch zweilhaft, weil der Rest Zu gring für den Aufwand ist ...
    - Performance auf CPU Seite
    - Du kriegst nen Bulk an C++ Entwicklern, den willst doch wohl ned Java oder C# beibringen auf Projektkosten ...

    Und Qt hat mittlerweile auch von der Entwicklungsgeschwindigkeit echt angezogen so das die unenrschiede nimmer so gross sind. Obwohl Qt wie alle anderen GUI-Libs recht komisches C++ sind ^^

    Ciao ...



  • Vielen Dank für eure Antworten.

    [...] wie [man] die Sprachmittel effektiv einsetzen kann[] [...]

    [...]"Within C++, there is a much smaller and cleaner language struggling to get out."[...]

    Genau darum gehts hier eigentlich in diesem Thread (theoretisch)

    @bad==ugly?:
    Ich glaub du hast nicht verstanden, was meine Intention war: Es geht mir nicht um Pro und Contra Argumente für die Verwendung von C++, genauso wenig wie es mir um Java vs. C++ geht (Ich sagte doch ausrücklich, mich interessiert nicht "Was ist die beste Programmiersprache"), sondern wenn schon feststeht, dass man C++ verwendet, was man gerne und oft verwenden/tun sollte und wovon man lieber die Finger lässt.



  • Deterministische Destruktoren ermöglichen neuen Herangehensweisen. Sowas kennt man in Java nicht, deswegen vermisst man das auch nicht. Aber wenn man sich in C++ daran gewöhnt hat, dann will man das auch nicht mehr vermissen. Ich schreibe ziemlich oft Scope Guard Objekte, die im Destruktor etwas machen. Die leg ich dann einfach auf dem Stack ab und muss mich nicht mehr großartig drum kümmern.



  • Java, C++ und C sind immer noch die Spitzenreiter. Wer alle drei beherrscht, und dann noch Javascript und C#, ist gefragt.



  • Erhard Henkes schrieb:

    Java, C++ und C sind immer noch die Spitzenreiter. Wer alle drei beherrscht, und dann noch Javascript und C#, ist gefragt.

    Man kann nicht alle drei Beherrschen. Das ist wie Theologe, Physiker und BWLer sein, nur nicht ganz so schlimm.



  • @Mechanics
    Ich schreibe auch in C# oft Scope-Guard Objekte Klassen. Halt mit IDisposable + using statt Destruktor. In Java ist im Prinzip das selbe möglich mit IAutoClosable + try-with-resources.
    Ist aber zugegebenermassen sehr mühsam und natürlich viel fehleranfälliger als mit C++ (da man immer manuell das using/try-with-resources schreiben muss).

    Und für Fälle wo man ein IDisposable/IAutoClosable als Member hat gibt es sowieso keinen Sprach-Support.



  • Mir hat das using und IDisposable nie gefallen. War allerdings bevor ich intensiv mit C++ gearbeitet habe, damals war mir das Scope Guard Idiom noch nicht wirklich geläufig.
    Und das ist eigentlich mehr so ein Workaround. Funktioniert auch nicht wirklich, wenn man die Scope Guards z.B. in eine Liste reinstecken will oder irgendwie rumreichen... Und wenn man das als Member haben will, muss die umgebende Klasse auch IDisposable implementieren und dann muss man sich auch noch drum kümmern, dass die in einem using Block benutzt wird. Ich glaub fast, der Aufwand lohnt sich in C# nicht.



  • Lol, was heisst "der Aufwand lohnt sich nicht"?
    Was gibt's denn für ne Alternative?

    Mechanics schrieb:

    Funktioniert auch nicht wirklich, wenn man die Scope Guards z.B. in eine Liste reinstecken will oder irgendwie rumreichen...

    Du steckst ScopeGuards in C++ in Listen und reichst sie rum? Ich glaube du hast nicht verstanden was ScopeGuard bedeutet.



  • hustbaer schrieb:

    Du steckst ScopeGuards in C++ in Listen

    Zumindest in Listen stecke ich sie schon. z.B. eine Liste von Lock Files (oder einfach Files), Handles, Events, irgendwas... Wenn sich eine Funktion halt mehrere "Handles" holt und die am Ende einfach alle freigeben muss.
    Ob ich die jemals rumgereicht habe, weiß ich grad nicht... Könnte ich mir aber auch vorstellen. z.B. initialisiert eine Funktion irgendwas und "lockt" alles, was man dann brauchen wird, und gibt das Guard Objekt dann an eine andere Funktion weiter, oder speichert das als Member in einem Objekt, das später stirbt, oder was auch immer. Ich kann mir alle möglichen Einsatzszenarien vorstellen. Notfalls steckt man den "Scope Guard" (vielleicht eine etwas erweiterte Definition davon) halt in einen shared_ptr, da ist man in C++ flexibel.



  • Naja gut, solche Fällen kann man in C# auch mit ein paar mehr oder weniger einfachen Hilfsklassen abdecken.

    Ich hab' z.B. eine Klasse DisposeGuard .
    Die hat ne statische DisposeGuard.Alloc<T>(T target) Methode mit der ein DisposeGuard<T> erzeugt wird. Wenn dabei eine Exception fliegt wird versucht target.Dispose() aufzurufen.
    Weiters hat der DisposeGuard<T> eine Property Target - diese lässt sich auch nachträglich ändern.
    Und eine ReleaseTarget Methode, die das Target auf null setzt und den alten Wert zurückgibt.

    Und wenn DisposeGuard<T> disposed wird, dann halt sein Target -- es sei denn es ist null .

    Damit kann man schon mal ein paar schöne Sachen machen wie

    using (var fileGuard = DisposeGuard.Alloc(File.Open...))
    {
        TuFileVorbereiten(fileGuard.Target);
        return fileGuard.ReleaseTarget();
    }
    

    Weiters kann man sich - wenn man es brauchen sollte - eine DisposeGroup basteln. Die halt dann eine Reihe von Targets hat. Usw.

    Keine Hexerei.



  • Hallo hustbaer,

    Ich finde deine Vorgehensweise bei der C#-Geschichte ziemlich interessant.
    Kannst du vieleicht etwas Code posten, wie das Ganze so aussieht.



  • @case
    Passt hier nicht so ganz rein, daher hab ich im C# Forum nen eigenen Thread gemacht: https://www.c-plusplus.net/forum/p2463692#2463692



  • hustbaer schrieb:

    Passt hier nicht so ganz rein

    😃


Anmelden zum Antworten