OOC mit Macros, Events, Listen und Exceptions



  • Hallo,

    ich hab mir vor nicht allzu langer Zeit in Java mal eine kleine Library geschrieben, die auf Basis von OpenGL diverses Zeugs gerendert hat. Zu Übungszwecken wollte ich die Library jetzt in C nachbauen, scheitere momentan aber noch an der Umsetzung.

    Ein Beispiel, wie ich mir das vorstelle:

    int main(int argc, char **argv)
    {
        GLDisplay *display = newGLDisplay("TestProject");
        TestRenderer *renderer = newTestRenderer();
        InputHandler *handler = newInputHandler(renderer, display);
        addGLEventListener(renderer);
        addKeyListener(handler);
        start();
        return 0;
    }
    

    In Java sah das so aus:

    public static void main(String[] args) {
    	GLDisplay display = GLDisplay.createGLDisplay("TestProject");
    	TestRenderer renderer = new TestRenderer();
    	InputHandler handler = new InputHandler(renderer, display);
    	display.addGLEventListener(renderer);
    	display.addKeyListener(handler);
    	display.start();
    }
    

    Das Erzeugen der Objekte über die new-Funktionen hab ich schon.
    Mein Problem sind momentan viel mehr die EventListener. Ich finde einfach nichts wie man diese in C am besten implementiert. Das sind im Grunde ja nichts weiter als Listen, die einfach durchlaufen werden, oder? Wie man Listen in C erzeugt weiß ich schon, nur fällt mir absolut nichts ein, wie ich diese Listen und deren Funktionen am sinnvollsten aufbauen/aufrufen soll.

    Kann mir da jemand ein bisschen was drüber sagen bzw. kennt jemand zu diesem Thema Literatur, Dokumente etc?

    Des weiteren suche ich gerade noch ein paar Dokumente wie man umfangreichere Macros baut. Hab Makros gefunden, mit denen man Exceptions nachbauen kann, deren Aufbau gefällt mir aber noch nicht richtig, weshalb ich sie umbauen wollte. Da ich aber nicht alles verstanden habe tue ich mag schwer die Makros zu optimieren. Kennt dazu jemand diverse Quellen, die ein wenig mehr erklären, als dass man mit Makros Code ersetzen kann, oder ist es am einfachsten wenn ich die Makros solange immer wieder durchlese und nachprogrammiere bis ich sie verstanden hab?



  • TestRenderer *renderer = newTestRenderer();

    ^^ ja, so sollte es hinhauen. 'methodenaufrufe' sehen dann etwa so aus

    Renderer_mach_was (renderer, ..., ...);
    

    was exceptions angeht, die würde ich nicht unbedingt in C nachbauen wollen. man kommt ganz gut ohne sie aus (sogar in Java). aber wenns unbedingt sein soll, liesse sich mit setjmp/longjmp sicherlich was machen.
    zu den makros: möglicherweise gibts gute tutorials (google: c preprocessor tricks?), ich kenne leider keins. ansonsten: selbst viel damit rumspielen macht den meister, ist ja mit allem so.
    ach ja, boost.preprocessor ist vielleicht einen blick wert. sieht zwar furchtbar aus (wie eigentlich alles von 'boost'), aber vielleicht gibts dir ein paar denkanstöße.
    🙂



  • ;fricky schrieb:

    was exceptions angeht, die würde ich nicht unbedingt in C nachbauen wollen. man kommt ganz gut ohne sie aus (sogar in Java). aber wenns unbedingt sein soll, liesse sich mit setjmp/longjmp sicherlich was machen.

    Ich wollte das hauptsächlich des Schreibaufwands und der Übersicht wegen implementieren. Aber das dürfte wahrscheinlich keinen großen Unterschied zu den Konsolenausgaben machen.

    ;fricky schrieb:

    zu den makros: möglicherweise gibts gute tutorials (google: c preprocessor tricks?), ich kenne leider keins. ansonsten: selbst viel damit rumspielen macht den meister, ist ja mit allem so.

    Ja, ist wahrscheinlich das Einfachste.

    ;fricky schrieb:

    boost.preprocessor

    omg, was'n das? Als Inspirationsquelle tut es das sicher, aber da bin ich ja Jahre beschäftigt bis ich da was versteh...



  • Frickys Protest zum Trotz: nimm doch einfach C++, du ersparst dir eine Menge Gefrickel und wenn du Exceptions willst, ist dein Code absolut nicht sicher. Mit Roh-Zeigern zu hantieren kann bei 'ner setjmp schnell 'nen Speicherleck auslösen, also lieber gleich verwalten mit std::tr1::shared_ptr<T> 😉



  • Ad aCTa schrieb:

    Frickys Protest zum Trotz: nimm doch einfach C++, du ersparst dir eine Menge Gefrickel und wenn du Exceptions willst, ist dein Code absolut nicht sicher. Mit Roh-Zeigern zu hantieren kann bei 'ner setjmp schnell 'nen Speicherleck auslösen, also lieber gleich verwalten mit std::tr1::shared_ptr<T> 😉

    Ad aCTas protest zum trotz: nimm keinesfalls C++, du wirst dich sonst auf eine menge gefrickel einlassen müssen, auch wenn es auf den ersten blick so scheint, als wäre es einfacher, aus code einer C-ähnlichen OO-sprache, code einer anderen C-ähnlichen OO-sprache zu machen.
    🙂



  • ;fricky schrieb:

    ... auch wenn es auf den ersten blick so scheint, als wäre es einfacher, aus code einer C-ähnlichen OO-sprache, code einer anderen C-ähnlichen OO-sprache zu machen.

    Du kannst sicher noch begründen, wieso das Offensichtliche nur "scheint" und es so viel einfacher ist, auf eine Sprache zu portieren, die viel weiter vom Original entfernt ist.

    Wenn man sich Gedanken darüber macht, Exceptions und OOP in C nachzubauen, ist C++ definitiv die bessere Wahl. Gerade wenn man von Java kommt und sowieso Mühe mit einigen Konzepten hat (manuelle Speicherverwaltung), kann man sich bei C++ wenigstens einen Teil der Arbeit durch RAII abnehmen lassen, während bei C wirklich alles von Hand freigegeben werden muss. Gleiches gilt für andere Konzepte, z.B. Vererbung, Polymorphie, Generics, Collections, und so weiter. Da ist man mit C++ um Lichtjahre näher bei Java.

    Der Code im ersten Beispiel zeigt bereits, dass 1:1 aus Java übernommen wurde und nirgends ein free() vorkommt. So hat man schneller Memory Leaks als man denkt.



  • Gefrickel-Vernichter schrieb:

    Du kannst sicher noch begründen, wieso das Offensichtliche nur "scheint" und es so viel einfacher ist, auf eine Sprache zu portieren, die viel weiter vom Original entfernt ist.

    ja, weil angebliche gemeinsamkeiten völlig anders funktionieren und er bei der umsetzung höllisch aufpassen muss, dass er nicht alle paar zeilen in eine der zahlreichen c++-fallgruben tappt. ein fehlendes 'free' z.b. ist dagegen ein lächerliches problem, das man leicht beheben kann. c++ ist auf dem oop-level nur für leute mit viel gespür und jahrelanger erfahrung richtig kontrollierbar. wenn er sich gut damit auskennt, kann er's in c++ machen, aber ohne das nötige wissen um die vielen 'no!-no!'s sehe ich bei c++ schwarz.
    🙂



  • ;fricky schrieb:

    Ad aCTas protest zum trotz: nimm keinesfalls C++

    Keine Sorge, das wird nicht passieren, ich beherrsche schon Java und eine weitere, auf objektorientierter Syntax aufgebauter Sprache, wollte ich ehrlich gesagt nicht lernen. 😉

    Gefrickel-Vernichter schrieb:

    Wenn man sich Gedanken darüber macht, Exceptions und OOP in C nachzubauen, ist C++ definitiv die bessere Wahl. Gerade wenn man von Java kommt und sowieso Mühe mit einigen Konzepten hat (manuelle Speicherverwaltung), kann man sich bei C++ wenigstens einen Teil der Arbeit durch RAII abnehmen lassen, während bei C wirklich alles von Hand freigegeben werden muss. Gleiches gilt für andere Konzepte, z.B. Vererbung, Polymorphie, Generics, Collections, und so weiter. Da ist man mit C++ um Lichtjahre näher bei Java.

    Ich finde, dass man hier sehr stark unterscheiden muss, zwischen Features, die mir die Arbeit deutlich erleichtern (new- und delete-Funktionen als De- und Konstruktoren) und Features die nur dazu da sind den Code objektorientierter aussehen zu lassen (wie Exceptions). Erstere halte ich für unabdingbar um vernünftig lesbaren Code produzieren zu können. Es erklärt sich von selbst, dass der Aufruf einer new-Funktion verständlicher ist als deren Inhalt in die aufrufende Funktion zu schreiben. Exceptions wollte ich deshalb haben, weil ich sie sehr nützlich sind um auf unvorhergesehene Fehler zu reagieren - der Aufwand sie zu implementieren ist allerdings sehr hoch und lang nicht so intuitiv wie eine new-Funktion.

    Ich halte es nicht für sinnvoll nur deshalb auf eine objektorientierte Sprache zu setzen, weil einem der Compiler hier zusätzliche Arbeit - wie Speicher zu allokieren - abnimmt. Exceptions sind sicherlich ein Grund eine solche Sprache zu nutzen. Konstuktoren sind dagegen so einfach zu realisieren, dass auf diese mit Sicherheit nicht nur in OO-Sprachen gesetzt werden sollte. Gerade in Bezug auf Libraries versuche ich immer abzuwägen in wie weit es sinnvoll ist Features zu implementieren, die dem Benutzer ermöglichen sich nicht um nervigen aber leider notwendigen Kleinkram wie beispielsweise der Speicherverwaltung zu kümmern.

    Ich finde die Diskussionen ob man beispielsweise nun C, C++, Java oder sonst eine Sprache einsetzen soll z.T. sehr lächerlich, da alle Sprachen komplett unterschiedliche Einsatzgebiete haben. Computerspielentwicklung mag mit C++ u.U. einfacher sein, da mir mit der OO-Syntax viel Arbeit abgenommen wird. Ich entwickle selbst gerade ein kleines 3D-Spiel mit OpenGL, werde dafür aber kein C++ nehmen, da ich an sich nicht an der Entwicklung eines Computerspiels sondern viel mehr an der Entwicklung der 3D-Engine interessiert bin. Das Spiel dient mir nur dazu die Möglichkeiten der Engine an einem Beispiel präsentieren zu können. Mich fasziniert an C nun mal, dass hier eben ganz klar im Vordergrund steht wie etwas funktioniert. Dieses Wissen bräuchte ich beispielsweise nicht wenn ich Direct3D einsetzen würde - hier stehen die Objekte im Vordergrund und eben deren geschicktester Zusammenbau. Im Gegensatz dazu würde ich niemals auf die Idee kommen einen Webserver mit C zu realisieren. Ich interessiere mich zwar stark dafür wie die Daten übers Internet versendet werden und bin gerade auch dabei einen Miniwebserver mit ASM zu programmieren - aber es ist deshalb absoluter Blödsinn Java nicht zu benutzen um eine richtige Webserverapplikation zu programmieren, nur weil ich dann nicht weiß wie das alles hardwareseitig abläuft. Ich hab schließlich keine fünf Jahre Zeit so etwas in C zu realisieren wenn es in Java innerhalb von ein paar Tagen geht. Hier gilt dann (wie auch für C++ und andere OO-Sprachen): Der Aufwand steht ganz klar im Vordergrund.

    Gefrickel-Vernichter schrieb:

    Der Code im ersten Beispiel zeigt bereits, dass 1:1 aus Java übernommen wurde und nirgends ein free() vorkommt. So hat man schneller Memory Leaks als man denkt.

    Hier habe ich bewusst keine Funktionen aufgerufen, die den allokierten Speicher wieder freigeben. Zum einen weil dort das Programm sowieso zu Ende war (beim terminieren des Prozesses wird der Speicher sowieso freigegeben), zum Anderen, weil cih wie bereits gesagt es für sinnvoll halte, dass solche Dinge im Hintergrund ablaufen (in meinem Java Programm wurde mit der Methode start() beispielsweise eine Endlosschleife aufgerufen, die erst beim Schließen des Programms wieder verlassen wurde. Vor dem terminieren des Prozesses wurden aber essentielle Methoden aufgerufen, die eben alles erledigt hatten was es noch vor dem Beenden zu erledigen gibt).

    Hmm, jetzt hab ich hier so einen Mammutbeitrag verfasst, ich hoffe den liest überhaupt jemand durch. 😮



  • Hmm, jetzt hab ich hier so einen Mammutbeitrag verfasst, ich hoffe den liest überhaupt jemand durch.

    Ich hab's gelesen. Aufmerksamkeit erkauft man sich durch das richtige Setzen von Satzzeichen -- schlimm dass es soweit gekommen ist.

    Keine Sorge, das wird nicht passieren, ich beherrsche schon Java und eine weitere, auf objektorientierter Syntax aufgebauter Sprache, wollte ich ehrlich gesagt nicht lernen.

    Da bist du im ANSI-Forum in guter Gesellschaft. Nicht jeder hier mag C++.
    🙂

    Btw würde mich interessieren, ob Objektorientierung wirklich nur Syntax-Sache ist. Könnte eine lustige Diskussion in RudP werden.

    Features die nur dazu da sind den Code objektorientierter aussehen zu lassen (wie Exceptions)

    Da bin ich drüber gestolpert, ich dachte immer, dass Exceptions wirklich was bringen, wenn man aus verschachtelten Aufrufen springen will. Aber da kommt's darauf an, was man meint, wenn man sagt: nur anders aussehen. Wenn man's völlig neu schreibt, sieht's auch nur anders aus.

    Jedenfalls wirst du in allen Fällen, wo du einfach nur aus dem dynamischen Kontext springen willst, mit man: longjmp genauso gut fahren.

    Wenn du magst, kannst du mir Beispiele für die Fälle geben, wo dir Exceptions abgehen, und ich will sehen, wie gut ich das ohne hinkriege.

    Hier habe ich bewusst keine Funktionen aufgerufen, die den allokierten Speicher wieder freigeben. Zum einen weil dort das Programm sowieso zu Ende war (beim terminieren des Prozesses wird der Speicher sowieso freigegeben), zum Anderen, weil cih wie bereits gesagt es für sinnvoll halte, dass solche Dinge im Hintergrund ablaufen

    Ich würde mich nicht darauf verlassen, dass der Speicher freigegeben wird, und der Standard besteht sicher darauf, dass free() aufgerufen wird. Ich kann mich da an einen subtilen Bug hier aus dem Forum erinnern, der mit fehlenden free's zu tun hatte und bei jedem OS/Compiler ein anderes Verhalten provoziert hat. War schwer zu finden, und ist schwer zu erinnern. Deshalb nimm mir bitte den Dogmatismus nicht übel, er kommt aus Erfahrung:

    Satz: Zu jedem malloc() muss ein free(), ohne Ausnahme.



  • Antoras schrieb:

    Exceptions wollte ich deshalb haben, weil ich sie sehr nützlich sind um auf unvorhergesehene Fehler zu reagieren

    das tun sie ja eigentlich nicht. du musst exceptions bewusst werfen, wenn du irgendwo eine fehlerquelle vermutest. bei unvorhersehbaren fehlern würdest du natürlich keine exception losschiessen, weil du ja garnicht erst darauf kommst, dass hier ein fehler auftreten könnte.

    Antoras schrieb:

    Mich fasziniert an C nun mal, dass hier eben ganz klar im Vordergrund steht wie etwas funktioniert.

    mich auch, und dass man vieles direkt und ohne ballast umsetzen kann. du kannst unmittelbar am code ablesen, was passieren wird. es wird nichts versteckt und es gibt keine heimlichen performance-bremsen und speicherfresser. das macht C zur ersten wahl bei hardwarenaher programmierung (naja, oder assembler, je nachdem...).

    Antoras schrieb:

    Im Gegensatz dazu würde ich niemals auf die Idee kommen einen Webserver mit C zu realisieren. Ich interessiere mich zwar stark dafür wie die Daten übers Internet versendet werden und bin gerade auch dabei einen Miniwebserver mit ASM zu programmieren.

    wie jetzt? einen webserver willste nicht in C aber in asm machen? das halte ich für keine gute idee. ok, wenn du spass daran hast, dann mach es, aber C finde ich gerade für sowas wie (embedded) webserver ideal. auch kannste C-code leichter auf andere targets portieren, was mit ASM nur geht, wenn der andere controller auf derselben CPU-familie basiert.

    µngbd schrieb:

    Da bist du im ANSI-Forum in guter Gesellschaft. Nicht jeder hier mag C++.

    ach? wirklich? *frech_grins*

    µngbd schrieb:

    Btw würde mich interessieren, ob Objektorientierung wirklich nur Syntax-Sache ist. Könnte eine lustige Diskussion in RudP werden.

    da gabs da alles schon viele male, blätter mal durch. OOP ist natürlich nicht sprachabhängig, nur isses mit sprachen einfacher zu realisieren, die's direkt unterstützen. es gibt aber auch einige beispiele, wobei OO in nicht-OO-sprachen erfolgreich umgesetzt wurde (GTK, windows-kernel, beides wurde in C geschrieben). andere beispiele sind grafische tools, die aus UML-diagrammen c-code erzeugen können: http://publib.boulder.ibm.com/infocenter/rsdp/v1r0m0/topic/com.ibm.help.download.rhapsody.doc/pdf/tutorialc.pdf

    µngbd schrieb:

    Deshalb nimm mir bitte den Dogmatismus nicht übel, er kommt aus Erfahrung:
    Satz: Zu jedem malloc() muss ein free(), ohne Ausnahme.

    wer dogmen anbringt, kriegt auch gleich 'ne exception vorgesetzt. lies pointercrashs postings hier: http://www.c-plusplus.net/forum/viewtopic-var-t-is-248648-and-start-is-33.html
    🙂



  • fricky schrieb:

    wer dogmen anbringt, kriegt auch gleich 'ne exception vorgesetzt.

    Hehe. Ich will versuchen, das Dogma zu retten:
    Satz: Zu jedem malloc() muss ein free(), ausser die Welt geht gerade unter.

    Antoras schrieb:

    Konstuktoren sind dagegen so einfach zu realisieren, dass auf diese mit Sicherheit nicht nur in OO-Sprachen gesetzt werden sollte.

    Ich hab's mir nocheinmal angesehen:

    GLDisplay *display = newGLDisplay("TestProject");
        TestRenderer *renderer = newTestRenderer();
        InputHandler *handler = newInputHandler(renderer, display);
    

    So ganz hat man damit noch keine Konstruktoren wie in Java, weil die dort etwas erzeugen, was von selbst wieder verschwindet. Wenn aber zB newGLDisplay() Aufrufe von malloc() enthält, solltest du dich, wie das Dogma befiehlt, auf free() einstellen.

    Wie du dafür sorgst, dass free() aufgerufen wird, ist aber völlig dir überlassen. Du könntest zB eine Liste mit Zeigern auf alle allozierten Blöcke unterhalten, um am Ende den Speicher automatisch freigeben zu können. Da würde es dann reichen, am Ende von main zu sagen: free_all(liste) .



  • µngbd schrieb:

    Hehe. Ich will versuchen, das Dogma zu retten:
    Satz: Zu jedem malloc() muss ein free(), ausser die Welt geht gerade unter.

    Wenn wir schon dabei sind, dann
    Satz: Zu jeden malloc() muss ein free() , sonst geht die Welt unter. 😉

    µngbd schrieb:

    Antoras schrieb:

    Konstuktoren sind dagegen so einfach zu realisieren, dass auf diese mit Sicherheit nicht nur in OO-Sprachen gesetzt werden sollte.

    Ich hab's mir nocheinmal angesehen:

    GLDisplay *display = newGLDisplay("TestProject");
        TestRenderer *renderer = newTestRenderer();
        InputHandler *handler = newInputHandler(renderer, display);
    

    So ganz hat man damit noch keine Konstruktoren wie in Java, weil die dort etwas erzeugen, was von selbst wieder verschwindet. Wenn aber zB newGLDisplay() Aufrufe von malloc() enthält, solltest du dich, wie das Dogma befiehlt, auf free() einstellen.

    Wie du dafür sorgst, dass free() aufgerufen wird, ist aber völlig dir überlassen. Du könntest zB eine Liste mit Zeigern auf alle allozierten Blöcke unterhalten, um am Ende den Speicher automatisch freigeben zu können. Da würde es dann reichen, am Ende von main zu sagen: free_all(liste) .

    Nur weil es "automatisch" (wie in Java/C++) nicht geht, heißt es nicht, dass sie nicht Konstruktoren sind. Sie reservieren Speicher, initialisieren das Objekt, sprich sie erledigen die Arbeit eines Konstruktors, somit sind sie auch Konstruktoren. Ein beliebter Fehler bei OO ist es zu glauben, dass bei OO alles "automatisch" gehen muss.

    Und was das free angeht, so kannst du den Code auch weiter vervollständigen:

    GLDisplay *display = newGLDisplay("TestProject");
    TestRenderer *renderer = newTestRenderer();
    InputHandler *handler = newInputHandler(renderer, display);
    
    ...
    
    free_InputHeandler(handler);
    free_TestRenderer(renderer);
    free_GLDisplay(display);
    

    wo ist dann das Problem? Und wenn du dir das sparen willst, dann kannst du dir einen mini-garbage collector für deine Objekte mit man: atexit(3) basteln.



  • supertux schrieb:

    µngbd schrieb:

    Satz: Zu jedem malloc() muss ein free(), ausser die Welt geht gerade unter.

    Satz: Zu jeden malloc() muss ein free() , sonst geht die Welt unter. 😉

    es gibt mindestens eine welt, in der zu jedem malloc() ein free gehört().
    🙂



  • supertux schrieb:

    Wenn wir schon dabei sind, dann
    Satz: Zu jeden malloc() muss ein free() , sonst geht die Welt unter. 😉

    Satz: Zu jedem free() gehört ein malloc() , sonst geht die Welt wirklich unter.



  • +gjm+ schrieb:

    supertux schrieb:

    Wenn wir schon dabei sind, dann
    Satz: Zu jeden malloc() muss ein free() , sonst geht die Welt unter. 😉

    Satz: Zu jedem free() gehört ein malloc() , sonst geht die Welt wirklich unter.

    Ihr seid sicher, daß die Welt malloc() und free() braucht, um nicht unterzugehen? Wenns nicht mehr braucht ... Cool! 🕶

    atexit kannte ich übrigens noch nicht ... man lernt nie aus ... 😉



  • +gjm+ schrieb:

    Satz: Zu jedem free() gehört ein malloc() , sonst geht die Welt wirklich unter.

    also stimmt es doch, was ich immer gegelaubt habe, dass unsere welt erst seit wenigen sekunden existiert.
    🙂



  • also stimmt es doch, was ich immer gegelaubt habe, dass unsere welt erst seit wenigen sekunden existiert.

    Witzig, das hab ich auch schon öfter gedacht. Aber irgendwie müßig, darüber nachzudenken.
    🙂

    atexit kannte ich übrigens noch nicht ... man lernt nie aus ...

    Ich hatte das für eine POSIX-Sache gehalten, aber atexit(3) sagt, daß es schon in C89 vorhanden war. Hätte ich schon öfter verwenden sollen... 🙄



  • µngbd schrieb:

    Ich hatte das für eine POSIX-Sache gehalten, aber atexit(3) sagt, daß es schon in C89 vorhanden war. Hätte ich schon öfter verwenden sollen... 🙄

    Jaa, ich hab auch gestaunt, aber ein paar meiner embedded- compiler kennen das nicht, da wird's das wiederentdeckte goto tun müssen ...
    🙄



  • pointercrash() schrieb:

    µngbd schrieb:

    Ich hatte das für eine POSIX-Sache gehalten, aber atexit(3) sagt, daß es schon in C89 vorhanden war. Hätte ich schon öfter verwenden sollen...

    Jaa, ich hab auch gestaunt, aber ein paar meiner embedded- compiler kennen das nicht...

    du kannst es leicht selbst einbauen, einfach ein kleines array von function pointern machen, dann den startup/exit-code so manipulieren, dass er, nachdem 'main' verlassen wurde, die liste durchgeht und alle funktionen darin aufruft.
    ach ja: wenn die main verlassen wird, geht die welt unter.
    🙂



  • ;fricky schrieb:

    wenn die main verlassen wird, geht die welt unter.
    🙂

    Achsoo, und ich dachte, wenn die welt verlassen wird, gibt's keine main() mehr.
    Bin eh grad am Abwägen, ob goto praktischer ist oder das Selbstschreiben eines C- Compilers und hab' dabei festgestellt, daß wir alle nur ein FIG- Port und somit völlig verdreht sind ... 😃
    Uiui, DAS war OT 🙄


Anmelden zum Antworten