Warum gibt es keinen Standard-Vergleichsoperator?



  • @otze: gutes Argument!
    Du kannst die betroffenen Daten aber in eine Basisklasse oder einen Member packen, und einen exception-sicheren operator= drumlegen. Ist zwar nicht immer eine Lösung, aber wenn man ein dutzend member hat...

    @Optimizer:
    Ich hatte bisher genügend Fälle, in denen bestimmte Operationen auf jeden member angewandt werden sollen. Wenn dir nicht gefällt das die "automatisch" da sind, kann ich das verstehen - aber gar nicht??? Vielleicht gefällt es Dir ja so besser:

    // nicht ganz echtes cpp
    class foo
    {
      // ... schön viele Member
      foo(foo const & rhs) memberwise;
      // operator= nicht definiert, also gibt es den nicht
    };
    

    oder noch hübscherer:

    // nicht ganz echtes cpp
    class foo
    {
      // ... schön viele Member
      foo & operator=(foo const & rhs)
      {
        try 
        {
          memberwise.operator=(rhs)
        }
        catch (...)
        {
          // now what to do? 
        }
      }
    };
    

    @Mastah: braucht man nicht mal macros dazu, geht mit entsprechenden membern bzw. basisklassen genauso:

    class nocopy
    {
      private:
        nocopy(nocopy const & rhs);
        nocopy & operator=(nocopy const & rhs);
    };
    
    class CMyClass : public nocopy
    {
       // oder auch: nocopy you_cannot_copyme;  
       // ... alles weitere
    };
    


  • peterchen schrieb:

    @otze: gutes Argument!
    Du kannst die betroffenen Daten aber in eine Basisklasse oder einen Member packen, und einen exception-sicheren operator= drumlegen. Ist zwar nicht immer eine Lösung, aber wenn man ein dutzend member hat...

    sicher kann man sonen op machen:

    foo& operator=(foo Operand){
        std::swap(*this,temp);
    }
    

    aber das wäre bei simpel klassen von der performance her absolute verschwendung...



  • Umm.. versteh ich nicht ganz...



  • otze schrieb:

    sicher kann man sonen op machen:

    foo& operator=(foo Operand){
        std::swap(*this,temp);
    }
    

    aber das wäre bei simpel klassen von der performance her absolute verschwendung...

    jo. evtl fehlt da noch

    foo temp(Operand);
    

    eigentlich sollte das der automatisch generierte sein. kommt ja vielleicht im c++-standard 2070 rein.
    simple typen, insbesondere pods kann der optimierer ja wieder raushauen. und wenn der typ fett genug ist, ne exception zu werfen, sind die kosten eher irrelevant.



  • peterchen schrieb:

    @Mastah: braucht man nicht mal macros dazu, geht mit entsprechenden membern bzw. basisklassen genauso:

    class nocopy
    {
      private:
        nocopy(nocopy const & rhs);
        nocopy & operator=(nocopy const & rhs);
    };
    
    class CMyClass : public nocopy
    {
       // oder auch: nocopy you_cannot_copyme;  
       // ... alles weitere
    };
    

    Ich finde es aber irgendwie unlogisch alle Klassen, deren Standard-Operatoren private sein sollen, von einer Basisklasse abzuleiten. Dann baue ich entweder jedes Mal Dummy-Operatoren per Copy&Paste rein oder ich bastle mir in irgendeinem versteckten Header ein hässliches Makro das die Arbeit für mich übernimmt, welches ich in eine private-Section reinhaue.

    // in ugly.hpp
    #define DENY_COPY(CLASS) CLASS(const CLASS&) {} \
                             CLASS& operator=(const CLASS&) { return *this }
    
    // in Class.hpp
    class Class
    {
      DENY_COPY(Class)
      int foo;
    public:
      Class::Class(int foo);
    };
    

    Ist ungetestet und ehrlich gesagt sieht es auch nicht so toll aus, aber wenn ich mir dadurch an vielen Stellen sinnlosen Code einsparen kann finde ich es nicht verwerflich. Ausser natürlich jemand nennt mir einen guten Grund, warum man es nicht so machen sollte oder zeigt mir eine bessere Lösung.

    Gruß,

    Christian

    EDIT: Also ich habe bisher nur kleinere Projekte mit 80-100 Klassen geschrieben. Ist es tatsächlich denkbar, dass man so viele Stellen hat wo ein Objekt nicht kopiert werden soll, dass sich eine - wie auch immer aussehende - Lösung als vereinfachend herausstellen würde?



  • wie gesagt, das gleiche bekommst du ja auch mit einem member hin, statt des Macros - mußt dem halt nur einen namen geben. (weiß nur nicht, ob der compiler den ohne speicher anlegen darf)

    Ich würd ein Macro nur verwenden, wenn es wirklich nicht anders geht.



  • peterchen schrieb:

    wie gesagt, das gleiche bekommst du ja auch mit einem member hin, statt des Macros - mußt dem halt nur einen namen geben. (weiß nur nicht, ob der compiler den ohne speicher anlegen darf)

    darf er nicht. aber dafür benutzt das makro netterweise mit #ifndef NDEBUG...
    also nullkosten in der release-version.



  • schad - also basisklasse 😉



  • peterchen schrieb:

    Ich würd ein Macro nur verwenden, wenn es wirklich nicht anders geht.

    Ich würde ehrlich gesagt so nen Mist garnicht verwenden. Mir passiert es so selten, dass ein Objekt nicht kopiert werden darf... Ich sage ja nur, dass ich es logischer finde als eine Basisklasse.



  • MaSTaH schrieb:

    peterchen schrieb:

    Ich würd ein Macro nur verwenden, wenn es wirklich nicht anders geht.

    Ich würde ehrlich gesagt so nen Mist garnicht verwenden. Mir passiert es so selten, dass ein Objekt nicht kopiert werden darf...

    falsche sichtweise.
    es geht nicht darum, ob der rechner beim kopieren abschmieren würde, sondern darum, ob das kopieren irgendeinen sinn hat.
    klar kann man nicht sachen wie File, Semaphore oder Process kopieren.
    klar ist es sinnvoll Datum, String oder Schuhgroesse zu kopieren.
    aber Person, Subnetz und FtpServer dürfen inhaltlich nicht kopiert werden. wer das tut, hat im programm sicher nen ligischen fehler.
    im weiteren ist es sogar so signifikant oft schwachfug, LinkedList, HashTable und FileHandle zu kopieren, daß man die ruhig auch verbieten sollte.
    lies "effektiv c++ programmieren". das tut helfen, die einstellung ein wenig in richtung bewußter bugvermeidung zu lenken.



  • Danke, ich habe es gelesen. Dass ich es nicht zu 100%ig verinnerlicht habe, da es für mich auch Bildung jenseits von C++ gibt mag man mir verzeihen. Ausserdem hast du meine Aussage vollkommen aus dem Zusammenhang gerissen. Ich habe nicht das verbieten des Copy-C'tors und des Zuweisungsoperators als Mist bezeichnet, sondern die "abkürzenden" Lösungen, von (denen ich selber eine gepostet hatte). In den paar Fällen, in denen man das Kopieren verbieten will ist es ein durchaus vertretbarer Aufwand die Sachen per Copy&Paste einzufügen.



  • volkard schrieb:

    otze schrieb:

    sicher kann man sonen op machen:

    foo& operator=(foo Operand){
        std::swap(*this,temp);
    }
    

    aber das wäre bei simpel klassen von der performance her absolute verschwendung...

    jo. evtl fehlt da noch

    foo temp(Operand);
    

    eigentlich sollte das der automatisch generierte sein. kommt ja vielleicht im c++-standard 2070 rein.
    simple typen, insbesondere pods kann der optimierer ja wieder raushauen. und wenn der typ fett genug ist, ne exception zu werfen, sind die kosten eher irrelevant.

    da muss kein foo temp(Operand) rein, ich übergeb ja keine referenz sondern eine kopie,aber der fehler im swap kommte daher, da ichd en operator erst in der form hatte:

    foo& operator=(const foo& Operand){
        foo temp(Operand);
        std::swap(*this,Operand);
    }
    

    dann hab ich aus dem operand ne kopie gemacht, und bei swap dann aus müdigkeit vergessen temp durch operand zu ersetzen^^



  • In den paar Fällen, in denen man das Kopieren verbieten will ist es ein durchaus vertretbarer Aufwand die Sachen per Copy&Paste einzufügen.

    Imo ist ein Makro bzw. eine Klasse hier kein Mittel zur Aufwandsreduzierung sondern zur Dokumentationsverbesserung. Ich bau mir ein Konzept "Nicht kopierbar" und verwende es da wo nötig. Mit den schönen Vorteilen wie:
    1. Ich habe nur genau einen Punkt an dem das Konzept implementiert ist. Nicht 3423. Damit habe ich auch nur eine Stelle, die ich im Zweifelsfalls ändern muss.

    2. Ich habe einen klaren sprechenden Namen der die Semantik nicht die Implementation in den Vordergrund stellt statt drei komischer Zeilen Quellcode die in drei Projekten sechsmal anders kommentiert wurden.

    3. Ich kann zusätzliche Dokumentation an genau eine Stelle schreiben.

    Usw...



  • @otze:
    warte...

    Wenn ich dich recht verstehe, willst du aus dem operator= 'ne Transaktion machen? d.h. wenn eine exception fliegt, soll der komplette alte Stand drin bleiben.

    Wenn aber jetzt im swap(*this, temp) (das ja wieder ein operator= macht) eine exception fliegt, bin ich doch nicht besser dran als vorher, oder?
    oder raff' ich's nicht?



  • in swap darf keine exception geworfen werden, swap hat die höchste sicherheitsgarantie: unter keinen umständen darf eine exception nach außen dringen



  • Das gilt aber nur für die speziellen Versionen von Container-Klassen, oder?
    Das globale Template ist ja als temp(a); a=b; b=temp implementiert (wüßte auch gar nicht wie sonst)



  • arrgh jetzt weis ich was du meinst, und keiner hier hats vorher gemerkt 🙄

    foo& operator=(const foo& Operand){
        foo temp(Operand);
        std::swap(*this,Operand);
    }
    

    des erzeugt ne schöne rekursion

    class foo{
            int member;
        public:
            foo& operator=(const foo& Operand){
                 foo temp(Operand);
                 std::swap(this->member,Operand.member);
            }
    

    das erzeugt keine rekursion, und nie fehler


Anmelden zum Antworten