Wieso gibt es keine gute Programmiersprache?



  • dachschaden schrieb:

    ...

    Ich verstehe überhaupt nicht wohin du argumentierst...? 😕

    Ich sehe C++ als Monster. Die überladenste Sprache überhaupt, die überhaupt alles kann. Und ich bin einer derjenigen, die das sehr positiv sehen.

    Aber Finnegan scheint ja zu klären, was an mir grad vorbeigeht.



  • auto& objekt = *(new (&objekt_auf_stack) Objekt{ 5 });
    

    Damit baue ich mir doch nur wieder einen Zeiger auf dem Stack, der dann aber wieder auf Speicher im Heap verweist, oder?

    Finnegan schrieb:

    Da stellt sich allerdings die Frage, ob man das so wirklich braucht.
    Warum nicht das Objekt erst in einem Scope deklarieren bei dem man weiss, dass es benötigt wird? ( if (objekt_benoetigt) { Objekt objekt{ 5 }; } )

    Weil ich meinen Code gerne beieinanderhalten würde?

    void foo(int bar)
    {
        irgendein_objekt;
    
        mach_was();
    
        if(bar)
        {
            initialisiere_objekt();
        }
        else
        {
            mach_noch_was();
        }
    
        mach_noch_mehr();
        und_noch_mehr();
    
        if(bar)
        {
            mach_was mit_objekt();
            mach_noch_mehr_mit_objekt();
        }
    
        und_noch_ein_bisschen_mehr();
        if(bar)
        {
            gib_objekt_frei();
        }
        return;
    }
    

    Hier gibt es eine Menge Code, die im Grunde gleich bleibt, und nur in einigen Fällen muss man was mit dem Objekt machen. Ein eigener Scope bedeutet viel redundanter Code. Das C-Modell erlaubt es mir, selbst zu bestimmen, was ich mit meinem Objekten mache, ohne mich verrenken zu müssen.

    Finnegan schrieb:

    Nun, man bezahlt auch das, was man bestellt - auch wenn man das 5-Gänge-Menü nicht isst.
    Das ist in C auch so, wenn man *_init() aufruft, und das Objekt dann nicht verwendet.

    Siehe oben.

    Finnegan schrieb:

    Mir fällt es etwas schwer diese CTOR/DTOR-Kritik nachzuvollziehen, da diese meines erachtens das mir mit Abstand wichtigste Feature von C++ sind.

    Ich sage nicht, dass es falsch ist. Nur ich hätte gerne schon die Kontrolle darüber, wann ich meine Objekte initialisiere und freigebe, ohne dass ich auf den Heap wechseln muss.

    Deswegen hätte ich ja gerne Templates in C. Die, die es nicht kümmert, können dann C++ verwenden, und die, die es kümmert, bleiben bei C. Templates sind nur teuer für den Compiler, damit kann ich leben.

    Finnegan schrieb:

    Hast du vielleicht ein konkreteres Beispiel, wo man angenehmer ohne Kostruktoren/Destruktoren untewegs ist?

    Ich habe sogar zwei aus meinem Hinterkopf:
    Das erste ist eine Liste mit auf Windows allozierten Pages, weil Windows kein VirtualReAlloc kennt. Deswegen habe ich ein Backup-Objekt auf dem Stack, welches ich dann befülle, wenn ich keine Erweiterungen mehr reservieren und woanders einen Speicherblock holen muss. Aufgrund der Struktur wird das Objekt an verschiedenen Stellen befüllt und ausgelesen, aber die Kosten für die Initialisierung (die immer mindestens eine einzelne Page für die größere Page-Liste ist) müsste ich in C++ immer mit mir tragen, wenn ich neuen Speicher reservieren will. Ich hätte dann einen Kernel-Call mehr drin, als notwendig wäre.

    Das zweite ist eine Funktion für einen ELF-Parser, die Program Header und Section Header-Metadaten rausextrahieren soll. Was der Nutzer will, wird über den Parameter angegeben. Weil ich aber sicherstellen will/muss, dass die Originaldaten nicht teilweise überschrieben werden, und dann bemerkt die Funktion einen Fehler und bricht mit halbgaren Informationen ab, übergebe ich meine eigenen, auf dem Stack angelegten "Parameter", in die geschrieben werden. Und am Ende müssen diese Daten auch noch mal aufbereitet werden (relative Offsets in absolute Zeiger, basierend auf bestimmten Feldern in der Dateistruktur).

    Finnegan schrieb:

    Meine Erfahrung ist nämlich eher, dass die ganzen goto error/goto cleanup ein ziemlicher Krampf sind, wenn man den Code korrekt und leckfrei hinbekommen will.

    Sprechende Labels können hier schon relativ helfen:

    if(!object_1_init())
        goto LABEL_ERROR_OBJECT_1_NO_INIT;
    
    if(!object_2_init())
        goto LABEL_ERROR_OBJECT_2_NO_INIT;
    ...
    

    Am Ende hast du dann oft nur eine Stelle, wo du wirklich aufpassen musst.



  • dachschaden schrieb:

    auto& objekt = *(new (&objekt_auf_stack) Objekt{ 5 });
    

    Damit baue ich mir doch nur wieder einen Zeiger auf dem Stack, der dann aber wieder auf Speicher im Heap verweist, oder?

    Nein. Das Objekt liegt auf dem Stack. std::aligned_storage<>::type ist letztendlich nur ein char[sizeof(Objekt)] -Array (Stack) mit korrektem Alignment für das Objekt.
    Bei dem new handelt es sich um ein placement new, welches das Objekt direkt in diesem char-Array konstruiert.
    objekt ist hier lediglich eine Referenz auf dieses Objekt ( new gibt Objekt -Pointer auf Stack zurück, Referenz wird mit dem dereferenzierten Pointer initialisiert).
    Das Ganze sollte äquivalent zur C-variante mit einem struct auf dem Stack und separater init() -Funktion sein.

    dachschaden schrieb:

    Finnegan schrieb:

    Da stellt sich allerdings die Frage, ob man das so wirklich braucht.
    Warum nicht das Objekt erst in einem Scope deklarieren bei dem man weiss, dass es benötigt wird? ( if (objekt_benoetigt) { Objekt objekt{ 5 }; } )

    Weil ich meinen Code gerne beieinanderhalten würde?

    Es stimmt schon, dass wenn man mit Scopes arbeitet, sich manchmal ein wenig mehr gedanken über die Abarbeitungsreihenfolge machen muss:

    void foo(int bar)
    {
        mach_was();
    
        if(!bar)
        {
            mach_noch_was();
        }
    
        mach_noch_mehr();
        und_noch_mehr();
    
        if (bar)
        {
            irgendein_objekt;
            mach_was mit_objekt();
            mach_noch_mehr_mit_objekt();
        }
    
        und_noch_ein_bisschen_mehr();
        return;
    }
    

    mach_was() , mach_noch_mehr() und und_noch_mehr() sind wohl unabhängig vom Objekt, da man sie auch aufrufen kann,
    wenn das Objekt nicht existiert (=nicht initialisiert wurde). Daher sollte man sie frei verschieben können, solange sie ihre relative Reihenfolge beibehalten.
    Falls doch eine Abhängigkeit vom Objekt besteht, ist tatächlich manchmal redundanter Code die einfachste Lösung, meiner Erfahrung nach lässt sich jedoch
    der meiste Code so formulieren, dass die Scopes auf natürliche Weise die gewünschte Lebenszeit des Objekts begrenzen.

    Für die wenigen Sonderfälle, wo sich das nicht elegant auflösen lässt, hilft entweder eine zusätzliche Status-Variable, oder man erstellt das Objekt zur Not
    eben auf dem Heap mit explizitem delete / unique_ptr<>.reset() oder verwendet meine oben vorgeschlagene Stack- new -Variante.
    Ich glaube diese Fälle sind allerdings derart selten, dass sie nicht wirklich als überzeugendes Argument gegen Konstruktoren/Destruktoren taugen.

    dachschaden schrieb:

    Finnegan schrieb:

    Hast du vielleicht ein konkreteres Beispiel, wo man angenehmer ohne Kostruktoren/Destruktoren untewegs ist?

    Ich habe sogar zwei aus meinem Hinterkopf:

    Virtuellen Speicher verwalten klingt schon sehr low-level. In solchen Fällen würde ich wahrscheinlich auch auf ein simples struct mit einer expliziten
    Initialisierungsfunktion zurückgreifen, oder nur sehr leichtgewichtige Klassen entwerfen.
    Für solche Spezialfälle hindert einen C++ übrigens nicht daran eine Klasse ohne expliziten Konstruktor/Destruktor zu
    schreiben (=default) und es genau so zu handhaben wie du es in C machen würdest.
    Meine Vorschläge wie u.a. das mit dem placement new bezogen sich übrigens auch nur auf Klassen, deren Konstruktoren du nicht selbst kontrollierst.

    dachschaden schrieb:

    Sprechende Labels können hier schon relativ helfen:

    if(!object_1_init())
        goto LABEL_ERROR_OBJECT_1_NO_INIT;
    
    if(!object_2_init())
        goto LABEL_ERROR_OBJECT_2_NO_INIT;
    ...
    

    Am Ende hast du dann oft nur eine Stelle, wo du wirklich aufpassen musst.

    Nimms mir nicht übel, aber ich bevorzuge ein simples return und den Aufräumcode an einer einzigen (!) zentralen Stelle im Destruktor,
    anstatt de facto für jede Funktion, wo ich das Objekt verwende einen eigenen "Destruktor" zu schreiben 😉

    Finnegan



  • Finnegan schrieb:

    mach_was() , mach_noch_mehr() und und_noch_mehr() sind wohl unabhängig vom Objekt, da man sie auch aufrufen kann,
    wenn das Objekt nicht existiert (=nicht initialisiert wurde).

    Das war schnell dahingetippter Beispielcode. In der Realität habe ich foo womöglich drei verschiedene Objekte übergeben (Hauptobjekt, Anzahl der Elemente, und Puffer oder so was ähnliches), und die übergebe ich dann auch den Unterfunktionen.

    Finnegan schrieb:

    Falls doch eine Abhängigkeit vom Objekt besteht, ist tatächlich manchmal redundanter Code die einfachste Lösung

    Danke für die Bestätigung, dass ich in solchen Fällen schlechter programmieren muss. Ich bleibe bei C, da habe ich mehr Kontrolle über alles und muss den Quellcode nicht runterziehen.

    Finnegan schrieb:

    Für die wenigen Sonderfälle, wo sich das nicht elegant auflösen lässt, hilft entweder eine zusätzliche Status-Variable, oder man erstellt das Objekt zur Not eben auf dem Heap mit explizitem delete / unique_ptr<>.reset() oder verwendet meine oben vorgeschlagene Stack- new -Variante.
    Ich glaube diese Fälle sind allerdings derart selten, dass sie nicht wirklich als überzeugendes Argument gegen Konstruktoren/Destruktoren taugen.

    Und deswegen hätte ich nur einfach gerne Templates in C, und den ganzen anderen Kram könnt ihr behalten. Deal? 🙂

    Finnegan schrieb:

    Virtuellen Speicher verwalten klingt schon sehr low-level. In solchen Fällen würde ich wahrscheinlich auch auf ein simples struct mit einer expliziten
    Initialisierungsfunktion zurückgreifen, oder nur sehr leichtgewichtige Klassen entwerfen.

    Sag an, genau das ist ja der Fall. Und trotzdem habe ich beim Leichtgewicht noch ein paar Kernel-Calls. Was aber immer noch besser als der Standard ist, was auf Windows bedeutet, sich den Cache und möglich auch den TLB zu ruinieren, weil man jetzt Daten von A nach B kopieren muss, weil der alte Speicher nicht ausreicht.

    (Und auf den TLB kann ich schon keine Rücksicht mehr nehmen, weil Windows im Gegensatz zu Linux die Infrastruktur verkackt hat - aber das ist ein anderes Thema).

    Finnegan schrieb:

    Für solche Spezialfälle hindert einen C++ übrigens nicht daran eine Klasse ohne expliziten Konstruktor/Destruktor zu
    schreiben (=default) und es genau so zu handhaben wie du es in C machen würdest.

    Wird ein leerer Konstruktoraufruf wegoptimiert?



  • dachschaden schrieb:

    Wird ein leerer Konstruktoraufruf wegoptimiert?

    Das macht wohl noch der schlechteste C++-Compiler.



  • Bin ich der einzige, der sich fragt, ob jemand der Diskussionsteilnehmer tatsächlich C++ in der Arbeit für ein großes Projekt verwendet?



  • Finnegan schrieb:

    dachschaden schrieb:

    Kann ich in C++ dem Compiler sagen, dass er nur ein bisschen Speicherbereich auf dem Stack reservieren soll für ein Objekt, und Initialisierung und Freigabe komplett mir überlassen soll? Wenn ja, will ich nichts gesagt haben.

    Ja, ich glaube es gibt kaum etwas, das in dieser wahnwitzigen Sprache nicht geht:

    #include <iostream>
    #include <type_traits>
    
    struct Objekt
    {
    	Objekt(int value) : value{ value } {}
    	void print() const { std::cout << value << std::endl; }
    	int value;
    };
    
    auto main() -> int
    {
    	// Reserviere Speicher für Objekt auf Stack.
    	typename std::aligned_storage<sizeof(Objekt), alignof(Objekt)>::type objekt_auf_stack;
    	// Erstelle Objekt.
    	auto& objekt = *(new (&objekt_auf_stack) Objekt{ 5 });
    	// Verwende Objekt.
    	objekt.print();
    	// Zerstöre Objekt.
    	objekt.~Objekt();
    }
    

    Versus:

    #include <iostream>
    
    struct Objekt
    {
    	Objekt(int value) : value{ value } {}
    	void print() const { std::cout << value << std::endl; }
    	int value;
    };
    
    int main()
    {
    	Objekt obj(5);
    	obj.print();
    	return 0;
    }
    

    No other comment included!



  • 5cript schrieb:

    hustbaer schrieb:

    5cript schrieb:

    invarianzfreie Programmierung

    Nie gehört. Link!

    Ich meinte "keine Invarianten". Invarianten oder "stille Gegebenheiten" salopp ausgedrückt sind Fehlerquellen, die Kapselung benötigen.

    Entweder musst du mich für viel blöder halten als ich bin, oder dich für viel schlauer als du bist.
    Ich weiss schon was Invarianten sind. Vielleicht solltest du dagegen mal lieber keine Begriffe verwenden die es nicht gibt/keiner sonst verwendet.

    5cript schrieb:

    Wenn man sowas vermeiden kann, kann man auch keine Kapselungsfehler machen oder Sachen vergessen.

    Natürlich kann man dann noch Kapseln. Und natürlich kann man dann noch Dinge vergessen. Manchmal schreibst du schon phenomänal unsinnigen Unsinn.

    5cript schrieb:

    EDIT: OOP ist Modellierung mit State. Der Versuch state zu bändigen. Aber das kann man auch anders lösen.

    Ja klar. Man kann alles immer irgendwie anders machen. Man kann z.B. jedes Programm auch in Assembler schreiben. Ist nur meistens net so spassig.



  • @dachschaden
    Wenn du keine Exceptions verwenden willst, dann verwende keine. Du kannst Exception Handling sogar per Compiler-Switch abdrehen.
    Und was Konstruktoren/Destruktoren angeht: Hallo, Inlining?

    Natürlich ist es trotzdem in den meisten Fällen beknackt so zu programmieren. Das Argument "C++ zwingt mir das auf" ist aber - davon unabhängig - meistens falsch.



  • dachschaden schrieb:

    Danke für die Bestätigung, dass ich in solchen Fällen schlechter programmieren muss. Ich bleibe bei C, da habe ich mehr Kontrolle über alles und muss den Quellcode nicht runterziehen.

    dachschaden schrieb:

    Und deswegen hätte ich nur einfach gerne Templates in C, und den ganzen anderen Kram könnt ihr behalten. Deal? 🙂

    Am Ende steht und fällt die ganze Argumentation damit, ob man es im Zweifelsfall genau so machen kann wie in C.
    Selbstverständlich kann man das, wenn man tatsächlich einen solchen, meiner Einschätzung nach extrem selten Sonderfall hat, der sich anders nicht elegant umformulieren lässt.

    Finnegan schrieb:

    Wird ein leerer Konstruktoraufruf wegoptimiert?

    Wird in C ein Aufruf wegoptimiert, bei dem zur Compile-Zeit bekannt ist, dass es sich um eine leere Funktion handelt?
    Wenn du tatsächlich solchen low-level Optimierungskram machst, solltest du das eigentlich wissen.

    dachschaden schrieb:

    Sag an, genau das ist ja der Fall. Und trotzdem habe ich beim Leichtgewicht noch ein paar Kernel-Calls. Was aber immer noch besser als der Standard ist, was auf Windows bedeutet, sich den Cache und möglich auch den TLB zu ruinieren, weil man jetzt Daten von A nach B kopieren muss, weil der alte Speicher nicht ausreicht.

    Na wer hat denn die Kernel-Calls in den Konstruktor geschrieben wenn nicht du? Der Compiler jedenfalls nicht.
    Wenn du das vermeiden willst, kannst du z.B. den virtuellen Speicher erst dann allozieren, wenn zum ersten mal darauf zugegriffen wird
    (z.B. in einer get() -Methode, wenn hinter dem internen Pointer noch kein Speicher steckt). Am Ende bestimmt immer noch der Programmierer was passiert.
    Oder du wendest eine der vielen genannten alternativen Methoden an, bis hin zu klassischem C-Style.

    Finnegan



  • Zeus schrieb:

    ... Versus: ...

    No other comment included!

    Die Anforderung war, dass zwar auf dem Stack immer Speicher für das Objekt reserviert werden soll, der Programmierer aber bestimmen möchte ob und wann es konstruiert und zerstört wird.
    Die zweite Variante erfüllt diese Anforderung nicht, da Reservierung von Stack-Speicher und Konstruktoraufruf nicht getrennt sind.

    Ferner war der Code lediglich ein Beispiel dafür wie man das für beliebige Klassen in C++ umsetzen kann und mitnichten eine Anleitung wie man immer und überall Objekte kostruieren sollte (Bloss nicht!).

    Finnegan



  • Nehmt mal diesen "Dachschaden" nicht so ernst. Der Typ ist ein Nazi. Bei solchen Leuten fehlen immer ein paar Latten am Zaun.



  • "Dachschaden" ist halt psychisch krank.



  • hustbaer schrieb:

    5cript schrieb:

    Wenn man sowas vermeiden kann, kann man auch keine Kapselungsfehler machen oder Sachen vergessen.

    Natürlich kann man dann noch Kapseln. Und natürlich kann man dann noch Dinge vergessen. Manchmal schreibst du schon phenomänal unsinnigen Unsinn.

    *weniger -> fixed
    &ja den Begriff verwendet niemand, war mir so aus dem Kopf gerutscht.

    Ich kann schließlich nicht alles aufzählen, wofür Kapselung gut ist 🙄

    Hey ich benutze auch manchmal friend um Methoden private zu machen.
    come kill me!



  • ich weiß echt nicht schrieb:

    "Dachschaden" ist halt psychisch krank.

    Er berichtete bereits von einer gescheiterten Kindheit. Ich entlockte ihm diesentsprechende Statements. Armes Schwein. Möget ihr seine fachlichen Äußerungen vor dem eben genannten Hintergrund betrachten, ergo: mit Vorsicht genießen. Ich nehme an, dass seine Erkrankung sein Urteilsvermögen trübt.



  • Finnegan schrieb:

    Zeus schrieb:

    ... Versus: ...

    No other comment included!

    Die Anforderung war, dass zwar auf dem Stack immer Speicher für das Objekt reserviert werden soll, der Programmierer aber bestimmen möchte ob und wann es konstruiert und zerstört wird.
    Die zweite Variante erfüllt diese Anforderung nicht, da Reservierung von Stack-Speicher und Konstruktoraufruf nicht getrennt sind.

    Ferner war der Code lediglich ein Beispiel dafür wie man das für beliebige Klassen in C++ umsetzen kann und mitnichten eine Anleitung wie man immer und überall Objekte kostruieren sollte (Bloss nicht!).

    Finnegan

    Vielleicht sollte nach den Sinn deines Beispiel fragen?



  • Zeus schrieb:

    Vielleicht sollte nach den Sinn deines Beispiel fragen?

    Vielleicht solltest du lesen, was du zitiert hast?

    Finnegan schrieb:

    dachschaden schrieb:

    Kann ich in C++ dem Compiler sagen, dass er nur ein bisschen Speicherbereich auf dem Stack reservieren soll für ein Objekt, und Initialisierung und Freigabe komplett mir überlassen soll? Wenn ja, will ich nichts gesagt haben.



  • Wird ja immer schlimmer.. schrieb:

    Zeus schrieb:

    Vielleicht sollte nach den Sinn deines Beispiel fragen?

    Vielleicht solltest du lesen, was du zitiert hast?

    Finnegan schrieb:

    dachschaden schrieb:

    Kann ich in C++ dem Compiler sagen, dass er nur ein bisschen Speicherbereich auf dem Stack reservieren soll für ein Objekt, und Initialisierung und Freigabe komplett mir überlassen soll? Wenn ja, will ich nichts gesagt haben.

    Vielleicht solltest du mehr effektive C++ Bücher lesen, damit man weißt dass es Unsinn ist. Statt zu sagen, dass es Unsinn ist, wird sehr unsinniger Code gepostet.



  • Andromeda schrieb:

    Er berichtete bereits von einer gescheiterten Kindheit. ...

    "Ad hominem" zu argumentieren kommt zumindest bei mir nicht besonders gut an, ungeachtet dessen wie stark oder schwach die Argumente des anderen sind.
    Wenn du nichts substantielles zur fachlichen Diskussion beizutragen hast, wäre zumindest ich dankbar, wenn du in Zukunft einfach schweigen würdest.

    Finnegan



  • Finnegan schrieb:

    Andromeda schrieb:

    Er berichtete bereits von einer gescheiterten Kindheit. ...

    "Ad hominem" zu argumentieren kommt zumindest bei mir nicht besonders gut an,

    Du hast natürlich Recht. Ad hominem ist niemals eine gültige Argumentationsfigur. Ich gebe zu, dass es mir in dem Fall nur darum ging, diesen Freak zu dissen und zu bashen. Ich mag nunmal keine Nazis. Bei Nazis geht mir das Messer in der Tasche auf. Egal, wo sie in Erscheinung treten.

    Btw, ich bin selber kein Freund von C++. Mir ist C auch viel sympathischer. Aber letztlich beruht das alles auf Erfahrungswerten. Wer mit anspruchsvollen Softwareprojekten auf leistungschwacher Hardware zu tun hatte (aus Kostengründen), und andererseits unter Zeitdruck Programme hinfrickeln durfte, die früh serienreif sein sollten und denen man nicht anmerken sollte, dass sie mit der heißen Nadel gestrickt waren, der wird mit C++ in keinem Fall glücklich.

    Ich bin der Meinung, dass C++ im mittleren Leistungsegment durchaus seine Berechtigung hat. Aber sobald ein managed environment aka .NET, Java-VM, Docker, usw. vorhanden ist, hat C++ ausgedient.


Anmelden zum Antworten