Wann sollte ich C# über C++11 benutzen?



  • cooky451 schrieb:

    @Xin Kannst du eine Situation konstruieren in der du 1. delete brauchst und 2. nicht gerade einen Container schreibst?

    Wer delete wirklich brauchen würde, könnte smart pointers nehmen, oder?
    Die könnte man auch in Containern, also sagen wir doch erstmal, daß man niemals delete braucht.



  • Ich finde in dieser Diskussion von wegen new und delete geht die eine wesentliche Erkenntnis leider unter, nämlich: Ressourcenverwaltung ist in C++ ein gelöstes Problem.



  • @volkard Smartpointer sind für mich auch nur Container. 😉

    dot schrieb:

    Ich finde in dieser Diskussion von wegen new und delete geht die eine wesentliche Erkenntnis leider unter, nämlich: Ressourcenverwaltung ist in C++ ein gelöstes Problem.

    Ja, aber dann kam

    Xin schrieb:

    Das fängt den Mehraufwand durch die manuelle Speicherverwaltung deutlich auf.

    Und alle so: Wowowowowow Alter, was'n das? 😃



  • cooky451 schrieb:

    @Xin Kannst du eine Situation konstruieren in der du 1. delete brauchst und 2. nicht gerade einen Container schreibst?

    Die Frage an sich ist schon fragwürdig gestellt, denn sie impliziert 1. dass es mit einem Smartpointer nicht gehen darf. Die Frage ist für mich nicht, ob ich es nicht auch mit einem SmartPtr lösen könnte, sondern ob ich überhaupt einen SmartPointer wünsche.
    Und 2. hast Du schon ein Beispiel ausgeschlossen: Ein Gegenbeispiel reicht oder glaubst Du ich spring hier jetzt, bis Du eine Liste hast und keinen Bock weitere Wünsche zu äußern?

    Schon alleine deswegen

    cooky451 schrieb:

    Xin schrieb:

    Das fängt den Mehraufwand durch die manuelle Speicherverwaltung deutlich auf.

    Und alle so: Wowowowowow Alter, was'n das? 😃

    Wowowowowow... Stinkefinger.

    Die Diskussion ging ursprünglich um C# oder C++. Dem "Wowowow"-Poserstyle brauche ich wohl nicht beizuwohnen.

    volkard schrieb:

    cooky451 schrieb:

    @Xin Kannst du eine Situation konstruieren in der du 1. delete brauchst und 2. nicht gerade einen Container schreibst?

    Wer delete wirklich brauchen würde, könnte smart pointers nehmen, oder?
    Die könnte man auch in Containern, also sagen wir doch erstmal, daß man niemals delete braucht.

    Ein SmartPointer ist ein Container für Pointer. "NIEMALS" braucht man delete...

    Wer unbedingt SmartPointer nutzen will und das für etwas weltbewegendes hält, dass alle seine Probleme löst, wunderbar.

    Wer das für die einzige Wahrheit hält, "Du sollst keine Pointer neben dem Smartpointer anbeten", dann haben wir was Religiöses. Und mit religiösen Fanatikern verschwendet man höchstens Zeit.

    Nochmals: Ich spreche mich nicht gegen SmartPointer aus, also packt euren Scheiterhaufen wieder ein. Aber da eh keiner liest, was schon geschrieben wurde...



  • Meine Behauptungen sind nur:
    - Man braucht delete nur in Containern. (Einschließlich Smartpointern.)
    - Container schreibt man (fast) nie, insbesondere wenn einem die Standardbibliothek zur Verfügung steht.
    => Ich sehe keinen Mehraufwand in der Speicherverwaltung. (unique_ptr tippen zu müssen zählt nicht für mich, da kann ich mich genau so über die ganzen Namespaces beschweren.)



  • Ich finde diese Diskussion durchaus erfrischend. Und das Resümee für mich ist mal wieder das, was ich an C++ so schätze: Ich habe die Wahl. Wenn ich den Speicher manuell verwalten möchte oder einen triftigen Grund dafür habe, dann kann ich es tun. Wenn ich es automatisch tun möchte, dann gibt es Werkzeuge bis hin zu GC. Und dazwischen alles, was das Herz begehrt.

    Es gibt einfach in C++ keine Grenzen. In fast jeder anderen Sprache gibt es die eine oder andere Einschränkung. Und sei es, dass ein GC die Objekte wegräumt auch wenn es für den Anwendungsfall nicht sinnvoll erscheint.

    Die vielen Möglichkeiten implizieren natürlich die Notwendigkeit, verantwortungsvoll mit diesen umzugehen und natürlich auch relativ viel Know how. ich brauche den Unterschied zwischen Stack- und Heapobjekten nicht zu kennen, wenn meine Sprache das nicht unterstützt.

    Ich persönlich bevorzuge die Freiheit von C++. Und wenn die Freiheit impliziert, dass ich ungebremst gegen die Wand fahren kann wenn ich will, dann ist das OK.



  • tntnet schrieb:

    ich brauche den Unterschied zwischen Stack- und Heapobjekten nicht zu kennen, wenn meine Sprache das nicht unterstützt.

    Nur um hier mal völlig unpassend einen kleinen Java-Seitenhieb zu plazieren: Irgendwie schon. Java kennt ja Stack-Variablen, man kann sich nur nicht aussuchen wo man was haben will. 😃 Aber dieses Verhalten muss auch einem Java-Anfänger klar sein:

    static void foo(MyObject o, double d)
    {
      o.foo(); // Verändert möglicherweise das worauf o zeigt
      d = 66.6; // Verändert nur die lokale Kopie
    }
    


  • cooky451 schrieb:

    tntnet schrieb:

    ich brauche den Unterschied zwischen Stack- und Heapobjekten nicht zu kennen, wenn meine Sprache das nicht unterstützt.

    Nur um hier mal völlig unpassend einen kleinen Java-Seitenhieb zu plazieren: Irgendwie schon. Java kennt ja Stack-Variablen, man kann sich nur nicht aussuchen wo man was haben will. 😃 Aber dieses Verhalten muss auch einem Java-Anfänger klar sein:

    static void foo(MyObject o, double d)
    {
      o.foo(); // Verändert möglicherweise das worauf o zeigt
      d = 66.6; // Verändert nur die lokale Kopie
    }
    

    Ja aber das hat absolut nichts mit tntnets Aussage zu tun.



  • Xin schrieb:

    Nochmal... ich schreibe in meinem ersten Posting, dass hier Java Vorteile hat, weil es in C++ einen syntaktischen Mehraufwand gibt und Ringreferenzen durch referenzzählende Shared-Pointer nicht aufgelöst werden können. Bei einer doppelt verketteten Liste gibt es immer mindestens ein Element, dass noch auf ein Node zeigt.

    Abgesehen davon, dass shared Ownership eine meiner Erfahrung nach extrem seltene Angelegenheit und referenzzählende Smartpointer daher ein sehr seltener Anblick sind, wäre das eigentliche Problem hier imo nicht, dass Referenzzählung eine Ringreferenz nicht auflösen kann, sondern die Tatsache, dass du überhaupt eine Ringreferenz hast. Dass zwei Objekte sich gegenseitig besitzen, macht rein prinzipiell keinen Sinn. Alle Links in einer doppelt verketteten Liste über shared_ptr oder etwas vergleichbares zu implementieren, ist imo einfach nur falsch (es steht im logischen Widerspruch zum Konzept einer Liste) und die daraus resultierenden Probleme sind als Folge der Missachtung von Naturgesetzen zu verstehen. Wenn du in einem Taucheranzug aus einem Flugzeug springst, ist imo jedenfalls nicht der Taucheranzug verantwortlich dafür, dass die Folgen der Gravitation potentiell unangenehm sind...

    Xin schrieb:

    Es ist kein Vorteil, das Wort "delete" in einem Quelltext zu vermeiden und durch ein anderes zu ersetzen.

    Wir ersetzen aber nicht einfach nur das Wort "delete" im Quelltext durch etwas anderes, sondern führen ein Konzept ein, welches die Formulierung einer Lösung vereinfacht.

    Xin schrieb:

    Ein Vorteil entsteht erst, wenn der Compiler Verantwortung übernehmen kann; ein in meinen Augen größerer Vorteil entsteht, wenn der Compiler den Entwickler auf nicht wahrgenommene Verantwortung hinweisen kann, ohne sie ihm abzunehmen: Das hält den Entwickler wachsam und stärkt sein Verständnis von dem, was er da schreibt. Im von mir benannten Listen-Beispiel verschleiert ein Referenzzähler die Verantwortung: Der Entwickler glaubt aus der Alltagserfahrung, sie abgegeben zu haben, muss aber tatsächlich selbst ran. Hier scheitern auch viele GCs (an die Haarspalter: viele heißt nicht alle).

    Hier hat C#/Java Vorteile im Vergleich zur manuellen Speicherverwaltung. Alleine die Tatsache, dass Du std::xxx_ptr schreiben musst ist ein manueller Akt. Du als Entwickler bist verantwortlich, das Richtige zu schreiben, in C#/Java schreibst Du "new bla()" und hast trotzdem kein Speicherleck (wenn man vom immensen Speicherverbrauch des GCs mal absieht)
    Das geht in C++ auch, aber auch da musst Du von Hand ran. Du musst immer die Verantwortung tragen, egal ob Du Dein "delete" in unique_ptr versteckst oder selbst schreibst. Es ist immer Mehraufwand und Verantwortung und nicht nur "Mehraufwand".

    Daraus werd ich irgendwie nicht schlau. Erst argumentierst du, wie wichtig es doch ist, dass eine Sprache dem Programmierer nicht die Möglichkeit gibt, "Verantwortung abzugeben" und im nächsten Absatz sagst du dann, dass Java und C# gegenüber C++ den Vorteil haben, dass der Programmierer in diesen Sprachen keine Verantwortung übernehmen muss!?

    Xin schrieb:

    Memoryleaks sind ein Anfängerproblem. RAII und SmartPointer sind zum Teil gültige Lösungen dafür, aber syntaktisch und im Fall von Shared-Pointern auch Leistungsmäßig Mehraufwand zu einfachen Pointern.

    Inwiefern sind shared_ptr deiner Meinung nach leistungsmäßiger Mehraufwand im Vergleich zu einfachen Pointern (ich vermute mal, du hast einfach übersehen, dass du da Äpfel und Bäume vergleichst)? Überhaupt: Kann es sein, dass du den Eindruck hast, dass hier gepredigt wird, dass alle Pointer durch shared_ptr ersetzt werden sollen? Falls ja: Das ist nicht der Fall, wer sowas behauptet, kann Smartpointer nicht verstanden haben. Bei RAII geht es um das Management von Ressourcen und nicht darum, das Konzept "Pointer" durch etwas anderes zu ersetzen. Smartpointer und Pointer haben eigentlich überhaupt nichts miteinander zu tun, die Bezeichnung "Smartpointer" ist (historisch bedingt) wohl leider etwas unglücklich gewählt. Wir sollten in Zukunft wohl besser einfach nur von "RAII Guards" oder so reden, um derartige Missverständnisse auszuschließen...



  • Gibt's eigentlich schon nen Compiler, der versucht einen GC für C++ zu implementieren? Seit C++11 ist es ja theoretisch möglich.



  • Wie genau meinst du das? C++ Pointer kann man generell nicht GCen denke ich. Ich meine, die kann man in eine Datei schreiben und wieder auslesen theoretisch, da muss man schon striktere Regeln in den Standard schreiben. 😃 Ansonsten: VS dürfte gcnew haben.



  • Zu irgendetwas müssen doch die Einführung einer GC-ABI in den Standard und Krams wie std::declare_reachable gut sein. 😉



  • tntnets Aussage gefällt mir sehr gut.

    dot schrieb:

    Xin schrieb:

    Ein Vorteil entsteht erst, wenn der Compiler Verantwortung übernehmen kann; ein in meinen Augen größerer Vorteil entsteht, wenn der Compiler den Entwickler auf nicht wahrgenommene Verantwortung hinweisen kann, ohne sie ihm abzunehmen: Das hält den Entwickler wachsam und stärkt sein Verständnis von dem, was er da schreibt. Im von mir benannten Listen-Beispiel verschleiert ein Referenzzähler die Verantwortung: Der Entwickler glaubt aus der Alltagserfahrung, sie abgegeben zu haben, muss aber tatsächlich selbst ran. Hier scheitern auch viele GCs (an die Haarspalter: viele heißt nicht alle).

    Hier hat C#/Java Vorteile im Vergleich zur manuellen Speicherverwaltung. Alleine die Tatsache, dass Du std::xxx_ptr schreiben musst ist ein manueller Akt. Du als Entwickler bist verantwortlich, das Richtige zu schreiben, in C#/Java schreibst Du "new bla()" und hast trotzdem kein Speicherleck (wenn man vom immensen Speicherverbrauch des GCs mal absieht)
    Das geht in C++ auch, aber auch da musst Du von Hand ran. Du musst immer die Verantwortung tragen, egal ob Du Dein "delete" in unique_ptr versteckst oder selbst schreibst. Es ist immer Mehraufwand und Verantwortung und nicht nur "Mehraufwand".

    Daraus werd ich irgendwie nicht schlau. Erst argumentierst du, wie wichtig es doch ist, dass eine Sprache dem Programmierer nicht die Möglichkeit gibt, "Verantwortung abzugeben" und im nächsten Absatz sagst du dann, dass Java und C# gegenüber C++ den Vorteil haben, dass der Programmierer in diesen Sprachen keine Verantwortung übernehmen muss!?

    Das andere habe ich nicht zitiert, weil wir prinzipiell gleicher Meinung sind. Auf das hier möchte ich eingehen.

    Ich beschäftige mich mit Programmiersprachendesign, habe C und C++-Programmierung unterrichtet und arbeite(te) als Java, C# und C++-Entwickler. Java hat zum Ziel die Ausbildung zu verkürzen, um schneller billige Entwickler zur Verfügung zu stellen. Um in Java überschaubarere Projekte zu realisieren, muss man nicht wirklich alles verstehen, was Java tut.
    C++ hingegen braucht fähige Spezialisten. Man muss also mehr lernen, die Ausbildung ist aufwendiger und die Entwickler teurer. Große Java-Projekte leiden aber unter dem mangelnden Verständnis von (vielen) Java-Entwicklern, C++ hingegen leidet darunter, dass hier auch nicht nur Spezialisten arbeiten können, sondern auch Semiprofessionelle sich durch die Arbeit zu Spezialisten ausbilden und dabei Fehler machen.
    Beide machen auf etwa gleichem Level Fehler: Java Entwickler aus Mangel an Möglichkeiten und entsprechend am Mangel sich überhaupt eines Problems bewusst zu werden, Semiprofessionelle C++-Entwickler aus Mangel an Kenntnissen.

    Das Abgeben von Verantwortung ist also nicht förderlich zum Verständnis - wenn ich mir keine Gedanken mache, werde ich kein Spezialist. Darauf zu Hoffen, dass der Entwickler aber bereits Spezialist ist, der schon alles verstanden hat und keine Fehler macht, ist auch unrealistisch. Am Schluss werden also beide Seiten Fehler machen.

    Der Entwickler muss klar sagen, was er will, um zu verstehen, was er tut. Ein GC nimmt Verantwortung ab, verschleiert aber die Konsequenzen. Auch ein SmartPointer ist in meinen Augen eine Verschleierung - man gibt die Verantwortung ab und hofft, dass das auch funktioniert. Man blendet eine Problematik aus, in dem man die Verantwortung einen SmartPointer oder einem GC anvertraut.

    Mir persönlich ist lieber, dass ich einen Segmentation Fault bekomme (Oh... ein Bug), als dass irgendeine nicht ausreichend beachtete Liste shared_ptr sammelt und ich ohne Memory-Leak den Speicher vollmülle. Sowas findet auch valgrind nicht. Ein Segmentation Fault hingegen ist eine sehr klare Aussage.

    Also entweder übergebe ich einer Sprache eine Verantwortung und kann mich bedingungslos darauf verlassen (z.B. virtuelle Funktionen in C++, statt OOP über vtables in C nachzubauen) oder ich muss wirklich verstehen, was ich tue.
    SmartPointer nutze ich auch, aber nur da, wo ich sie benutzen will und nicht grundsätzlich.

    dot schrieb:

    Xin schrieb:

    Memoryleaks sind ein Anfängerproblem. RAII und SmartPointer sind zum Teil gültige Lösungen dafür, aber syntaktisch und im Fall von Shared-Pointern auch Leistungsmäßig Mehraufwand zu einfachen Pointern.

    Inwiefern sind shared_ptr deiner Meinung nach leistungsmäßiger Mehraufwand im Vergleich zu einfachen Pointern (ich vermute mal, du hast einfach übersehen, dass du da Äpfel und Bäume vergleichst)?

    Wie ich schon schrieb: Ein SharedPtr kostet Rechenzeit beim kopieren.

    Und syntaktisch muss ich mehr tippen, beim Lesen, muss ich mehr Text entziffern.

    Das kann man als unerheblich bezeichnen, ich persönlich sehe das als Manko in C++. Hier hat eben Java den Vorteil, dass der Klassenname alleine bereits die Referenz repräsentiert: Der Code wirkt einfacher und aufgeräumter.

    Auch der rechnerische Mehraufwand hakt bei manchen Aufgaben. Ich schreibe nicht umsonst Container, die per PlacementNew operator new, operator delete und den jeweiligen Standard-Konstruktor vermeiden. Alles zu teuer.

    dot schrieb:

    Überhaupt: Kann es sein, dass du den Eindruck hast, dass hier gepredigt wird, dass alle Pointer durch shared_ptr ersetzt werden sollen? Falls ja: Das ist nicht der Fall, wer sowas behauptet, kann Smartpointer nicht verstanden haben.

    Ich würde nicht alle unterschreiben - das wäre zu leicht zu widerlegen, aber das Predigen nehme durchaus wahr.

    dot schrieb:

    Smartpointer und Pointer haben eigentlich überhaupt nichts miteinander zu tun

    Das wiederum würde ich jetzt auch nicht so unterschreiben. ^^



  • Ich habe festgestellt, dass der Einsatz von Smartpointer ebenfalls sehr selten ist, eben weil man weinig besitzende Pointer im allgemeinen braucht. Fast alle Situationen werden durch Container abgedeckt. Und das auch schon in C++03.

    Ja aber das hat absolut nichts mit tntnets Aussage zu tun.

    Ich finde tntnets Aussage sehr allgemein und mehr blabla. Da kann ich nicht viel gegen sagen.

    Es ist kein Vorteil, das Wort "delete" in einem Quelltext zu vermeiden und durch ein anderes zu ersetzen.

    Nein, es wird weggelassen, nicht ersetzt. Das hat aber nichts mit ausblenden oder hoffen auf Funktionsfaehigkeit zu tun.

    Kannst du eine Situation konstruieren in der du 1. delete brauchst und 2. nicht gerade einen Container schreibst?

    Wer delete wirklich brauchen würde, könnte smart pointers nehmen, oder?
    Die könnte man auch in Containern, also sagen wir doch erstmal, daß man niemals delete braucht.

    Brauchen ist so ... sagen wir mal elegant. Hier mein Beispiel fuer das einzige delete im aktuellen Projekt. Es ist meine elegante Loesung. Nebenbedingungen: Es kann nur C++03 benutzt werden und zusaetzliche Abhaengigkeiten wie boost sind nicht gern gesehen. Fuer die Erstellung eines Threads wird ein Helper konstruiert um Parameter (auf Zeiger und built-in Datentypen beschraenkt) der Threadfunktion typsicher mitzugeben. Dabei muss garantiert werden, dass das Hilfsobjekt solange existiert, biss der neue Thread alle Parameter uebernommen hat. Wie also new/delete einsparen, wenn man fuer diesen einen ueberschaubaren Fall nicht extra einen Smartpointer programmieren moechte. Aus dem Kopf mal niedergeschrieben:

    Benutzung:

    thread t(&obj_type:run, &obj);
    
    struct thread
    {
        template<typename F, typename T>
        thread(F f, T t) : joinable_(false), id_(0)
        {
            thread_helper<F, T>* ph = new thread_helper<F, T>(f, t);
    
            int const res = pthread_create(&id_, 0, &thread_helper<F, T>::call, &ph);
            if (res)
            {
    			delete ph;
                throw std::runtime_error("thread creation failed");
    		}
            joinable_ = true;
        }
    
        // .. join und detach member functions
    
    private:
        template<typename F, typename T>
        struct thread_helper
        {
            thread_helper(F f, T t) : t(t), f(f), m(m) {}
    
            static void* call(void* data)
            {
                thread_helper* th = (thread_helper*)data;
                T t = th->t;
                F f = th->f;
                delete th;
                (t->*f)();
                return 0;
            }
            T t;
            F f;
        };
        thread();
        thread(thread const&);
        thread const& operator=(thread const&);
    
        bool joinable_;
        pthread_t id_;
    };
    


  • Wäre der thread_helper als Attribut des threads nicht auch recht lecker?



  • volkard schrieb:

    Wäre der thread_helper als Attribut des threads nicht auch recht lecker?
    Oder gleich t und T.

    Diskutabel, aber ich wollte die Template (Memberfuntion und Typ) nicht nach aussen tragen, sie sollten automatisch deduziert werden. Als Vorbild diente natuerlich Boost bzw. Threads in C++11. Erstellen von Threads sollte so einfach wie moeglich sein. Natuerlich ist diese Klasse sehr eingeschraenkt, was alles im Thread laufen kann. Hier sind es Objekte und Memberfunktion ohne Parameter. Das Konzept kann angepasst werden, aber fuer jeden Fall brauche ich einen extra Helper. Aktuell koennen Memberfunktionen mit keinen, einen oder zwei Parameter gestartet werden. Das hier dient nur als Beispiel.

    Ist aber auch nicht der Punkt. Der Punkt hier ist, dass der Stack nicht benutzt werden kann, da der Konstruktor des Threadobjektes verlassen werden kann, bevor der neue Thread die Chance hat zu arbeiten. Natuerlich kann mittels Barrier, Condition Variables oder Locks der Konstruktor so lange warten, bis der neue Thread die Parameter kopiert hat. Aber dann haette ich nicht die Abhaengigkeit zu new/delete sondern eben zu anderen Sachen.



  • Also würde

    auto t=make_thread(&obj_type:run, &obj);
    

    auch reichen.
    Ok, den Typ von T zu ändern hat Nachteile. Threads würden sicherlich auch gerne in gemeinsamen Listen verwaltet werden. Dafür dann virtual auspacken? Also zahlste dein new eben gleich und kriegst die irgendwo nötige Polymorphie durch den void-Pointer.



  • Ok, den Typ von T zu ändern hat Nachteile.Threads würden sicherlich auch gerne in gemeinsamen Listen verwaltet werden. Dafür dann virtual auspacken?

    Ich verstehe nicht ganz. Thread verwaltet nur eine Thread-Id und ist prinzipiell fit fuer Kontainer (wenn ich es erlauben wuerde).

    Ja, aber make_thread unterscheidet sich jetzt nicht von meinem Konstruktor. Ist natuerlich konsistenter zu C++ weil, make_pair oder make_shared schon bekannt sein sollten. Ich denk' drueber nach. 🙂 Und zu auto, ... tja C++03, sonst wuerde ich auch keine eigene Threadklasse schreiben und sowas wie Type erasure anstreben. auto macht type erasure obsolete.



  • knivil schrieb:

    Ok, den Typ von T zu ändern hat Nachteile.Threads würden sicherlich auch gerne in gemeinsamen Listen verwaltet werden. Dafür dann virtual auspacken?

    Ich verstehe nicht ganz. Thread verwaltet nur eine Thread-Id und ist prinzipiell fit fuer Kontainer (wenn ich es erlauben wuerde).

    Ja, Dein Thread. Aber wenn der thread_helper zum Attribut wird, haben verschiedene thread_ausprägungen ja verschiedene größen und passen nicht mehr ein einen gemeinsamen Container. Pro Ausprägung einen Container könnte man machen, und das würde bei meinen Programmen sogar angemessen sein. Aber würde man es flexibler haben wollen, käme dann noch eine Klasse thread_base, von der alle threads erben. Und schon wird es irgendwie wieder unfein. Da ist Deine Lösung hübscher, die zickt einfach nicht rum.


Anmelden zum Antworten