vlib



  • DrGreenthumb schrieb:

    füg doch mal include "assert.hpp" hinzu...

    in RawArray.hpp?
    ist drin. seit ner stunde afair.

    ach, wie ich das hasse, daß der eine compiler das durchläßt und der andere da nen fehler meldet.



  • volkard schrieb:

    DrGreenthumb schrieb:

    füg doch mal include "assert.hpp" hinzu...

    in RawArray.hpp?
    ist drin. seit ner stunde afair.

    ach, wie ich das hasse, daß der eine compiler das durchläßt und der andere da nen fehler meldet.

    hehe, mach mal einen monotone oder svn server auf 🙂

    btw. für welchen Compiler testest du? GCC 4?



  • kingruedi schrieb:

    Error.hpp:6: Warnung: ‘class Error’ has virtual functions but non-virtual destructor

    klarer programmierfehler. ist raus.

    template<typename RawObject,Size size,bool onHeap>
    

    Warum benutzt du dieses bool-Flag? Finde ich
    (a) nicht sonderderlich verständlich. Wenn jemand nun so etwas sieht und die vlib nicht kennt, dann weiß er nicht was es bedeutet

    die vlib soll nicht nur replacement der standardlib sein, sondern ein paar sachen schon anders machen. da flag wird schon ein wenig deutlicher bei

    template<typename Data,Size size=0,bool onHeap=size==0>
    class Array...
    

    ich will, daß ein Array mit echt vielen elementen ganz von allein auf dem heap liegt und ein kleies Array ganz von allein auf dem stack. das war bei std::vector immer ein großes argernis, daß der zwingend new benutzt und entsprechend langsam für kleine mengen ist.

    Array<int,10,true> a;
    

    normalerweise schreibt der benutzer ja

    Array<int,10> a;
    

    und benutzt das flag eher nie.

    (b) was ist wenn man nun in Shared Memory oder in irgend einem spezial Speicher allokieren will, daher würde ich wie in der stdlib Allokatoren vorziehen.

    ich habe noch nie std::vector zusammen mit einem eigenen allokator verwendet. hab die allokatoren erstmal vertagt. mus noch viel besser werden, um die gescheit hinzukriegen. aber bedarf ist schon da. der primzahlenzähler ist mit eigenem allokator 20% schneller.



  • kingruedi schrieb:

    hehe, mach mal einen monotone oder svn server auf 🙂

    theoretisch hab ich ja einen. muß mir mal erzählen lassen, wie man sowas verwendet.

    btw. für welchen Compiler testest du? GCC 4?

    windows: ms toolkit 2005
    windows: mingw (gcc 3.4.2)
    linux: gcc 3.3.5-20050130



  • Bei mir gehts immer noch nicht...

    mkdir .outMakefile
    echo keep > .outMakefile/keep
    g++  -Wall -Werror -pipe -fno-rtti -O3 -s -DNDEBUG -fomit-frame-pointer -ffast-math -fno-enforce-eh-specs -fmerge-all-constants -c -o .outMakefile/assert.o -MD assert.cpp
    g++  -Wall -Werror -pipe -fno-rtti -O3 -s -DNDEBUG -fomit-frame-pointer -ffast-math -fno-enforce-eh-specs -fmerge-all-constants -c -o .outMakefile/credits.o -MD credits.cpp
    g++  -Wall -Werror -pipe -fno-rtti -O3 -s -DNDEBUG -fomit-frame-pointer -ffast-math -fno-enforce-eh-specs -fmerge-all-constants -c -o .outMakefile/FileReader.o -MD FileReader.cpp
    g++  -Wall -Werror -pipe -fno-rtti -O3 -s -DNDEBUG -fomit-frame-pointer -ffast-math -fno-enforce-eh-specs -fmerge-all-constants -c -o .outMakefile/FileWriter.o -MD FileWriter.cpp
    g++  -Wall -Werror -pipe -fno-rtti -O3 -s -DNDEBUG -fomit-frame-pointer -ffast-math -fno-enforce-eh-specs -fmerge-all-constants -c -o .outMakefile/iostream.o -MD iostream.cpp
    g++  -Wall -Werror -pipe -fno-rtti -O3 -s -DNDEBUG -fomit-frame-pointer -ffast-math -fno-enforce-eh-specs -fmerge-all-constants -c -o .outMakefile/main.o -MD main.cpp
    g++  -Wall -Werror -pipe -fno-rtti -O3 -s -DNDEBUG -fomit-frame-pointer -ffast-math -fno-enforce-eh-specs -fmerge-all-constants -c -o .outMakefile/os.o -MD os.cpp
    g++  -Wall -Werror -pipe -fno-rtti -O3 -s -DNDEBUG -fomit-frame-pointer -ffast-math -fno-enforce-eh-specs -fmerge-all-constants -c -o .outMakefile/prime.o -MD prime.cpp
    g++  -Wall -Werror -pipe -fno-rtti -O3 -s -DNDEBUG -fomit-frame-pointer -ffast-math -fno-enforce-eh-specs -fmerge-all-constants -c -o .outMakefile/PrimeGenerator.o -MD PrimeGenerator.cpp
    swap.hpp: In function `void swap(Data&, Data&) [with Data = Size]':
    Stack.hpp:52:   instantiated from `void swap(Stack<PrimeGenerator::Runner>&, Stack<PrimeGenerator::Runner>&)'
    PrimeGenerator.hpp:95:   instantiated from here
    swap.hpp:6: Fehler: Abfrage des Elementes »swap« in »a«, das vom Nicht-Klassentyp »long unsigned int« ist
    make: *** [.outMakefile/PrimeGenerator.o] Fehler 1
    

    Mit meinem i386-pc-linux-gnu-g++ gehts übrigens. Wird wohl was mit der größe von datentypen z tun haben...



  • ness schrieb:

    Bei mir gehts immer noch nicht...

    jup. momentchen.



  • jetzt müßte es gehen.
    lag an den datentypen.
    weil ich swap nicht mehr für alle typen angeboten hab, sondern nur noch explizit angebe, was geswapped werden kann, und weil ich swap für unsigned long noch nicht hatte, konnte er bei dir Size nicht swappen.



  • kingruedi schrieb:

    hehe, mach mal einen monotone oder svn server auf 🙂

    eigentlich würde ein script um wget reichen, das bescheidsagt, wenn sich die vhlib.tar.bz geändert hat.



  • volkard schrieb:

    die vlib soll nicht nur replacement der standardlib sein, sondern ein paar sachen schon anders machen. da flag wird schon ein wenig deutlicher bei

    Aber das Allokatoren Konzept finde ich schon ziemlich gut, da es flexibel und ohne Overhead ist. Man braucht es zwar selten, aber wenn man mal Speicher woanders anlegen will, dann ist man aufgeschmissen und es kostet ja keinen Overhead.

    Aber true und false Flags sind leider nicht so eindeutig. Mach doch ein

    enum { OnHeap=true, OnStack=false };
    

    🙂



  • Was mir persöhnlich nicht gefällt ist, dass ASSERT eine Ausnahme wirft. Das schafft meiner Meinung nach viel mehr Probleme als es löst.

    Zum Beispiel kann es in keinem Destruktor eingesetzt werden.

    Desweiteren kann man mittels catch(...) die Assertion auch abfangen also ist es nicht mehr sicher, dass eine Assertion immer knallt.

    Wenn eine Assertion fehlschlägt dann ist der Wurm in der Programlogik also ist es nicht sicher, dass die Destruktoren ihren Job erledigen. Sollten einer deshalb während dem Stackunwinding abstürtzen dann merkt man nicht mal, dass die Assertion fehlschlug.

    Man weiß nichtmal von wo die Ausnahme flog und man fragt wie zum Teufel der Programfaden dahin gelangt ist. Die Assertion schlug ja nicht fehl und danach gibt es kein throw mehr.

    Desweiteren kann es selbst richtige Programlogik als falsch erscheinen lassen. Beispiel:

    Obj::Obj(){
      ptr = new int;
      *ptr = 1;
    }
    void Obj::foo()
    {
      // Mach irgendetwas, dass *ptr 0 ist was es aber nicht sein sollte
    }
    void Obj::swap(Obj&other)
    {
      int*temp = this->ptr;
      ptr = other.ptr;
      ASSERT(*ptr != 0);
      other.ptr = temp;
      ASSERT(*other.ptr != 0);
    }
    Obj::~Obj()
    {
      delete ptr; // Ein Genuß wie das hier knallt
    }
    

    Das eigentliche Problem ist zwar, dass foo auf 0 testen sollte, aber du weißt ja hoffentlich wie schnell man ein ASSERT vergessen hat. in swap hat man sich gedacht, dass ein zusätzliches ASSERT nie überflüssig ist und teste sobald ein Objekt wieder in einem funktionierenden Zustand ist. this ist korrekt beim ersten ASSERT, other noch nicht wird es aber am Ende von swap sein.

    So nun hat foo aber gewütet und das erste ASSERT wirft eine Ausnahme, other bleibt deshalb korrupt. Nun kommt es zum doppelten Löschen im Destruktor. Der Debugger wird sich hier melden. Der Programmierer wird denken, dass ihm irgendwo ein Flüchtigkeitsfehler bei der Speicherverwaltung unterlaufen ist und diese überprüfen und dabei ist diese fehlerfrei.

    Ganz unabhängig davon ist

    #define CHECK(cond) if(cond);else raiseAssertError(#cond,__FILE__,__LINE__)
    

    fehlerhaft. Beispiel:

    namespace foo
    {
      void raiseAssertError(char const* cond,char const* file,int line);
      void bar()
      {
        CHECK(false); // Hier wird foo::raiseAssertError aufgerufen
      }
    }
    

    Das müsste

    #define CHECK(cond) if(cond);else ::raiseAssertError(#cond,__FILE__,__LINE__)
    

    heißen.

    Dann währe ein FAIL(), FAIL_MSG(msg), CHECK_MSG(what, msg), ASSERT_MSG(what, msg) sicher nicht verkehrt.

    In addModUnguarded würde ich die Argumente noch mit einer Assertion überprüfen.

    Ganz dumme Frage aber was bezweks du mit

    inline void operator delete(void*,void*) throw(){
    }
    

    😕



  • Ben04 schrieb:

    Ganz dumme Frage aber was bezweks du mit

    inline void operator delete(void*,void*) throw(){
    }
    

    😕

    der wird aufgerufen, wenn der entsprechende placement new eine excpetion wirft. ok, das tut er zwar nicht, aber weiß das auch der linker?



  • volkard schrieb:

    template<typename Iterator>
    void sort(Iterator begin,Iterator end);
    

    allerdings sofort probleme, denn die iteratoren können noch nicht zum beispiel op+(Iterartir,Size). das müßte dann nachgerüctet werden.

    Das wär weniger das Problem, denke/hoffe ich, hab eh grad mit meinen LinkedList Iteratoren zu kämpfen. :p
    Aber range-checked sind sie ja, oder hab ich's falsch verstanden?

    volkard schrieb:

    Dauert dir aber wohl zu lange, bis ich da fertig wär.

    kann man nicht wissen. ich brauche noch keinen. wenn ich einen brauche, schreib ich halt einen. wenn du vorher einen hast, sag bescheid.

    Ok, ansonsten kann ich nur (kennst du wahrscheinlich schon, aber wurst) www.sortieralgorithmen.de empfehlen.



  • Ben04 schrieb:

    Was mir persöhnlich nicht gefällt ist, dass ASSERT eine Ausnahme wirft. Das schafft meiner Meinung nach viel mehr Probleme als es löst.

    aber es löst auch probleme. und zwar darf ja durch ein einfache arraygrenzenüberschreitung irgendwo nicht auf einmal die datenbank wo anders in nem inkonsistenten zustand gelassen werden.

    Zum Beispiel kann es in keinem Destruktor eingesetzt werden.

    jup. zur zeit hoffe ich, daß assertions in destruktoren was extrem seltenes sind.

    Desweiteren kann man mittels catch(...) die Assertion auch abfangen also ist es nicht mehr sicher, dass eine Assertion immer knallt.

    man darf eben AssertError nicht fangen, ohne es weiterzuwerfen, außer man ist die main().

    Wenn eine Assertion fehlschlägt dann ist der Wurm in der Programlogik also ist es nicht sicher, dass die Destruktoren ihren Job erledigen.

    da wäre ich mir nicht so sicher.

    Sollten einer deshalb während dem Stackunwinding abstürtzen dann merkt man nicht mal, dass die Assertion fehlschlug.

    jo. bisher kenne ich einen kritischen fall. wenn ein FileWriter destruiert wird und deswegen flush() aufruft und die platte voll ist.

    Desweiteren kann es selbst richtige Programlogik als falsch erscheinen lassen. Beispiel:

    ...
    void Obj::swap(Obj&other)
    {
      int*temp = this->ptr;
      ptr = other.ptr;
      ASSERT(*ptr != 0);
      other.ptr = temp;
      ASSERT(*other.ptr != 0);
    }
    Obj::~Obj()
    {
      delete ptr; // Ein Genuß wie das hier knallt
    }
    

    [/quote]
    aua. nun könnte ich sagen, daß man assert wie ne ausgabe auch halten sollte, also auf keinen fall im exception-unsicheren teil veranstalten. das würde ich auch sofort, wenn ich mir sicher wäre, daß es klappt. aber die range checked iteratoren werfen ja auch. und das kann ja oft passieren, wenn man am wenigsten damit rechnet. ein ausweg wäre ne datei assertlog.txt, die zuätzlich geschrieben wird. in den seltenen fällen, die du anführtst, hätte man dann wenigstens noch ne chance, zu sehen, was los war.

    Das müsste

    #define CHECK(cond) if(cond);else ::raiseAssertError(#cond,__FILE__,__LINE__)
    

    heißen.

    ok. ist gefixt.

    In addModUnguarded würde ich die Argumente noch mit einer Assertion überprüfen.

    ok. in allen Unguarded-sachen.

    hab vor jahren nach exceptionwerfendem assert umgestellt und es war bisher immer prima. meyers schlägt das auch vor. normalerweise soll dieses assert, wenn der debugger an ist, auch noch __asm int 3; ausführen, was einen auf der fehlerzeile in den debugger wirft. damit kann man dann auch die schwierigen fälle sehr gut verfolgen. normalerweise, hält man einfach das programm an, wenn man den fehler repariert hat. ist ne kritische sache unten am leben, die ber dtor gerettet werden muss, sagt man dem debugger halt, er soll bis programmende laufen.

    ich will es drauf ankommen lassen, ob die von die genannten probleme sich wirklich zeigen werden und lass ASSERT vorläufig werfen.



  • GPC schrieb:

    Aber range-checked sind sie ja, oder hab ich's falsch verstanden?

    ja, sind sie. das könnte recht praktisch werden, wenn man nen sortieralgo baut. bin mal gespannt.



  • volkard schrieb:

    jetzt müßte es gehen.
    lag an den datentypen.
    weil ich swap nicht mehr für alle typen angeboten hab, sondern nur noch explizit angebe, was geswapped werden kann, und weil ich swap für unsigned long noch nicht hatte, konnte er bei dir Size nicht swappen.

    Ja, sowas hab ich mich auch gedacht...
    Aber, was bezweckst du damit? Ich meine, zumindest primitivtypen könntest du doch normal swappen?

    namespace help
    {
        template<class T,bool simple_swap>
        struct sh
        {
            static inline void swap(T& a,T& b)
            {
                a.swap(b);
            };
        };
        template<class T>
        struct sh<T,true>
        {
            static inline void swap(T& a,T& b)
            {
                T tmp(a);
                a=b;
                b=tmp;
            };
        };
    };
    
    template<class T>
    inline void swap(T& a,T& b)
    {
        help::sh<T,type_traits<T>::is_primitive||type_traits<T>::is_pointer>::swap(a,b);
    };
    


  • ness schrieb:

    Aber, was bezweckst du damit?

    daß jeder, der ne klasse baut, die swappen können soll, das auch anbieten muss. dabei garantiert er dann auch, daß swap exceptionsicher ist.

    [/quote]Ich meine, zumindest primitivtypen könntest du doch normal swappen?[/quote]
    jo. nix dagegen.

    namespace help
    {
        template<class T,bool simple_swap>
        struct sh
        {
            static inline void swap(T& a,T& b)
            {
                a.swap(b);
            };
        };
        template<class T>
        struct sh<T,true>
        {
            static inline void swap(T& a,T& b)
            {
                T tmp(a);
                a=b;
                b=tmp;
            };
        };
    };
    
    template<class T>
    inline void swap(T& a,T& b)
    {
        help::sh<T,type_traits<T>::is_primitive||type_traits<T>::is_pointer>::swap(a,b);
    };
    

    hab keine type_traits.
    für pointer hab ich schon

    template<typename Data>
    inline void swap(Data*& a,Data*& b){
    	Data* tmp(a);
    	a=b;
    	b=tmp;
    }
    

    was auch zu funktionieren scheint.

    nu bin ich am überlegen, ob ich die primitiven typen lieber aufzählen sollte, oder ob ich meta-programming-monster baue.
    vol umfang des codes sollte es ähnlich sein.



  • volkard schrieb:

    aber es löst auch probleme. und zwar darf ja durch ein einfache arraygrenzenüberschreitung irgendwo nicht auf einmal die datenbank wo anders in nem inkonsistenten zustand gelassen werden.

    Kommt natürlich auf die Situation an. Ich würd wenn immer möglich ein Program nicht ungetestet auf eine wichtige Datenbank loslassen. Immer zuerst auf entweder eine Kopie der Richtigen oder auf eine die mit Zufallsdaten gefüllt ist loslassen, jedenfals eine die man getrost reseten kann. Aber du hast recht dies ist in der Tat ein Problem.

    volkard schrieb:

    Desweiteren kann man mittels catch(...) die Assertion auch abfangen also ist es nicht mehr sicher, dass eine Assertion immer knallt.

    man darf eben AssertError nicht fangen, ohne es weiterzuwerfen, außer man ist die main().

    Ich hab schon viele Artikel im Internet die zu folgendem geraten haben gesehen:

    class Foo{
    public:
      ~Foo(){
        try{
          bar();
        }catch(...){
        }
      }
    };
    

    Das wäre ja schon beinahe tötlich für dein ASSERT. Da ein fehlerhaftes Program ja gar nicht mal abstürtzt.

    Vielleicht wäre folgender Ansatz eine Lösung:

    // Nicht von Error ableiten, soll ja nicht 
    // gefangen werden oder?
    class AssertError 
    {
      static unsigned instance_count;
    public:
      AssertError(const char*msg){
        // Hiermit ist sichergestellt, dass die Assertion 
        // auch gemeldet wird.
        cerr<<"Assert: "<<msg<<endl;
        ++instance_count;
      }
      AssertError(const AssertError&){
        ++instance_count;
      }
      ~AssertError(){
        // hiermit ist sichergestellt, dass das Assert nicht abgefangen
        // wird
        --instance_count;
        if(instance_count == 0)
          abort();
      }
    };
    unsigned AssertError::instance_count = 0;
    


  • Ben04 schrieb:

    volkard schrieb:

    aber es löst auch probleme. und zwar darf ja durch ein einfache arraygrenzenüberschreitung irgendwo nicht auf einmal die datenbank wo anders in nem inkonsistenten zustand gelassen werden.

    Ich hab schon viele Artikel im Internet die zu folgendem geraten haben gesehen:

    class Foo{
    public:
      ~Foo(){
        try{
          bar();
        }catch(...){
        }
      }
    };
    

    Das wäre ja schon beinahe tötlich für dein ASSERT. Da ein fehlerhaftes Program ja gar nicht mal abstürtzt.

    Genaugenommen wäre es tödlich, besagten "Rat" anzunehmen und nicht, das ASSERT so zu machen. Ein Destruktor soll natürlich prinzipiell keine Exception werfen. Das heißt aber nicht, dass er mit try-catch umgeben werden soll. Das heißt eher, dass wenn der Destruktor was loggt und er konnte es nicht in die Datei schreiben, dass speziell dieser Schreibvorgang gesichert wird und die restliche Arbeit des Destruktors weiterläuft und die von anderen anstehende Destruktoren auch.
    Ich finde das ASSERT gut so, es spricht gar nichts dagegen, den technisch einwandfreien Exception-Mechanismus zu nutzen. Es ist auch nicht so, dass volkard damit was völlig neues auf die Beine gestellt hat, das ist gängige Praxis in C++ und anderen Sprachen.



  • Ben04 schrieb:

    Ich hab schon viele Artikel im Internet die zu folgendem geraten haben gesehen:

    class Foo{
    public:
      ~Foo(){
        try{
          bar();
        }catch(...){
        }
      }
    };
    

    lol. das ist auch sachlich falsch, weil ich dann bei ner nicht-fertig-geschriebenen datei denke, sie sei fertig. dann schon lieber fürstlich abschmieren. ich stelle mir nur mal vor, mein textverarbeitungsprogramm würde mit solchen methoden schreiben.

    Das wäre ja schon beinahe tötlich für dein ASSERT. Da ein fehlerhaftes Program ja gar nicht mal abstürtzt.

    zur vlib muß dann auch ein style-guide her. jo, ich schreib ihn gerade mal.

    Vielleicht wäre folgender Ansatz eine Lösung:
    [cpp]
    // Nicht von Error ableiten, soll ja nicht
    // gefangen werden oder?
    [/quote]
    stimmt. soll in der main gefangen werden, da mach ich halt die zeile dazu.

    den zähler überleg ich mir noch, ob mir irgendein fall einfällt, wo der stört.



  • destruktoren dürfen nix tun, was normale laufzeitfehler verursachen könnte. man wird nicht mit assert fragen, ob daten auf die platte geschrieben werden konnten. man wird beim schreiben trotzdem immer testen (bei mir mit SYSCHECK) und gegebenenfalls exceptions werfen.
    ich kann nur verhindern, daß bei destruktoren unfug passiert, wenn ich dort nichts aufrufe, was laufzeitfehler haben könnte. insofern ist flush() im destruktor von FileReader schlecht.
    andererseits muß aber genau dieses flush aufgerufen werden.

    eigentlich muss nur die erste exception geworfen werden und bei allen folgenden wird nicht geworfen, sondern auggegeben. also in raiseAssertError das if, das den zähler prüft.


Anmelden zum Antworten