Was würdet ihr an C++ verbessern?



  • Moin,

    ich habe gerade folgendes gefunden:

    In C with Classes, it was possible to define a function that would implicitly be called before every call of every member function(except the constructor)and another that would be implicitly called before every return from every member function. They were called call and return functions. They were used to provide synchronization for the monitor class in the original task library...

    class monitor : object{
        call() {/*grab lock*/}
    
        return(){/*release lock*/}
    
    };
    

    In dem Text schreibt Stroustrup weiter, dass er die "Leute"(aka C with Classes/C++-User anno 1980) nicht davon überzeugen konnte, dieses Feature zu nutzen, weshalb es später wieder rausflog.

    Ich hatte mit Synchronisation, Multihtreading etc. bisher noch nicht allzu viel zu tun. Ehrlich gesagt hab ich davon Null Ahnung. Also daher die Frage:
    Worin bestünde der Vorteil dieses Konstrukts gegenüber nem Mutex, der z.B. in nem Wrapper haust und nur diesen einen Methodenaufruf lockt? So:

    class monitor_wrapper{
    private:
        monitor m;
    
    public:
        void do_something_locked(){
            mutex_type mutex;          // Evt. als Member?
            mutex.lock()
            m.do_something();
            mutex.unlock();
        }
    };
    

    Natürlich hat im C with Classes Design keinerlei Kosten für das Erstellen des Mutex. Dafür jedoch zahlt man mit zwei zusätzlichen Methodenaufrufen (call und return), die jedoch höchst wahrscheinlich geinlined werden können. Man bekommt aber ein Problem, sobald eine Methode keinerlei call(), return()-Funktionalität benötigt und ein zusätzlicher Switch eingebaut werden muss. Sehe ich das richtig?
    Außerdem sehe bisher außer dem Locking-Mechanismus keinerlei andere sinnvolle Anwendung darin. Wie seht ihr das?

    * aus Thomas J. Bergin, History of Programming Languages II, 1996



  • Man bekommt aber ein Problem, sobald eine Methode keinerlei call(), return()-Funktionalität benötigt und ein zusätzlicher Switch eingebaut werden muss. Sehe ich das richtig?

    Ich sehe da kein Problem. Du kannst ja auch ein Array aus 1 Mio. PODs mit delete wegmachen ohne dass 1 Mio. mal der triviale Destruktor aufgerufen wird.
    Genau so würde es dann auch triviale call() und return() Funktionen geben - d.h. die würden dann einfach nicht aufgerufen.

    Und Stroustrup ist Performance-Freak. Du kannst davon ausgehen, dass er es nicht vorgeschlagen hätte, wenn man auch in Klassen wo man es nicht verwendet dafür bezahlen müsste.

    Ich hatte mit Synchronisation, Multihtreading etc. bisher noch nicht allzu viel zu tun. Ehrlich gesagt hab ich davon Null Ahnung. Also daher die Frage:
    Worin bestünde der Vorteil dieses Konstrukts gegenüber nem Mutex, der z.B. in nem Wrapper haust und nur diesen einen Methodenaufruf lockt?

    Darin dass man den Code nicht mit Hand schreiben muss? 🙂
    Ich sehe darin aber eher einen Nachteil: es würde dazu verleiten mehr Klassen "intern" zu synchronisieren. Und das ist schlecht da es a) zu einem "false sense of security" führen kann und b) oft die Performance verschlechtert, da unnötig oft gelockt wird. Wenn man es "aussen" macht kann man "blocken", also die Mutex 1x locken, dann ein paar Memberfunktionen aufrufen und dann 1x unlocken. Und da Mutexen locken/unlocken relativ teuer ist...

    So:

    class monitor_wrapper{
    private:
        monitor m;
    
    public:
        void do_something_locked(){
            mutex_type mutex;          // Evt. als Member?
            mutex.lock()
            m.do_something();
            mutex.unlock();
        }
    };
    

    Ja, ganz sicher als Member, sonst geht das schonmal gar nicht. Die Mutex muss ja immer die selbe sein, wenn jeder Aufrufer seine eigene bekommt können sich die ja unmöglich untereinander synchronisieren.

    Natürlich hat im C with Classes Design keinerlei Kosten für das Erstellen des Mutex.

    Wie kommst du denn auf die Idee? Die Mutex müsste man genau so selbst bereitstellen wie man es auch in C++ muss.

    Außerdem sehe bisher außer dem Locking-Mechanismus keinerlei andere sinnvolle Anwendung darin.

    Man könnte Klassen-Invarianten damit prüfen.

    class foo
    {
    // ...
    
        void call()
        {
    #ifndef NDEBUG
            if (m_nesting_count == 0)
                check_invariants();
            m_nesting_count++;
    #endif
        }
    
        void return()
        {
    #ifndef NDEBUG
            m_nesting_count--;
            if (m_nesting_count == 0)
                check_invariants();
    #endif
        }
    };
    


  • Mit diesem call()/return()-Konstrukt lässt sich aber nur schwer ausdrücken, wenn man das Feature nur für einen Teil der Methoden benötigt. Um beim Locking zu bleiben - bei einer Getter-Methode würde das reserieren und freigeben des Locks möglicherweise mer Zeit benötigen als das eigentliche Lesen der Daten.

    Zum Thema: Nachdem ich seit ca. einem Jahr nicht mehr aktiv in C++ programmiere (mein Arbeitgeber setzt eine andere Entwicklungsumgebung ein, die angeblich auch Objektorientierung beherrscht), vermisse ich eigentlich die Eleganz der Sprache.



  • CStoll schrieb:

    Mit diesem call()/return()-Konstrukt lässt sich aber nur schwer ausdrücken, wenn man das Feature nur für einen Teil der Methoden benötigt. Um beim Locking zu bleiben - bei einer Getter-Methode würde das reserieren und freigeben des Locks möglicherweise mer Zeit benötigen als das eigentliche Lesen der Daten.

    Man muss auch beim lesen synchronisieren!



  • Finde ich jetzt überhaupt nicht schade, dass call() und return() nicht Bestandteil von C++ sind. Implizite Features wie "rufe vor und nach allen Funktionen eine Zusatzfunktion auf" sind immer heikel, weil man schnell den Überblick verliert, wo sie zum Zuge kommen. Zum Beispiel will man das bei privaten Funktionen wahrscheinlich nicht.

    Ausserdem kann man ja lokale Mutexes sehr elegant mit RAII lösen.

    void Class::Function()
    {
        ScopedLock lock(mutex);
        // wird am Ende des Scopes automatisch freigegeben - im
        // Gegensatz zu lock() und unlock() sogar bei Exceptions
    }
    


  • rüdiger schrieb:

    CStoll schrieb:

    Mit diesem call()/return()-Konstrukt lässt sich aber nur schwer ausdrücken, wenn man das Feature nur für einen Teil der Methoden benötigt. Um beim Locking zu bleiben - bei einer Getter-Methode würde das reserieren und freigeben des Locks möglicherweise mer Zeit benötigen als das eigentliche Lesen der Daten.

    Man muss auch beim lesen synchronisieren!

    Aber nicht so gründlich 😉

    Ich habe z.B. einiges mit SQL zu tun - und dort unterscheidet man zwischen shared lock (zum Lesen der Daten - das können mehrere Programme gleichzeitig, ohne sich zu stören) und exclusiv lock (zum Schreiben der Daten - da darf zwischendurch niemand anderes in die Nähe der Daten, um Inkonsistenzen zu vermeiden). Das kannst du sicher analog auch auf "normale" Daten im Programm anwenden, die threadsicher verarbeitet werden müssen.


Anmelden zum Antworten