Wie swap in eigener Klasse machen?



  • #include <vector>
    #include <iostream>
    
    class A
    {
    public:
        const int size = 9001;
        std::vector<int> v;
        A(){
            std::cout << "cons"<< std::endl;        
            v = std::vector<int>(size);
        }
        A(const A& a){
            std::cout << "copy"<< std::endl;
            v.reserve(size);
            for(auto it: a.v)
                v.push_back(it);
        }
    
        A& operator= (const A& a){
            std::cout << "="<< std::endl;
    
            v.clear();
            v.reserve(size);
            for(auto it: a.v)
                v.push_back(it);
        }
    };
    
    int main(){
       A b,c;
       std::swap(b,c);
    }
    

    Wenn ich das nun ausführe kommt: "copy = =".
    Es wird also erst eine temporäres Objekt erstellt (t(b)). Dann b=a und a=t
    (ggf anders herum).
    Der Vektor ist aber über 9000 Elemente lang und wird quasi drei mal kopiert. Könnte man das auch anders implementieren?



  • Der Vector ist 9001 Elemente lang.





  • Warum tauschst du die Vektoren nicht einfach? Oder nimmst das andere. Oder ähnlich.



  • manni66 schrieb:

    http://en.cppreference.com/w/cpp/concept/Swappable

    Ich kann zwar meiner Klasse ein normales swap machen, dass wird dann aber nicht verwendet bei zB std::sort. Wie würde das gehen? (Nur wenn ich ohne std swap schreibe geht es)

    Und wie schreibe ich:
    http://en.cppreference.com/w/cpp/types/is_swappable
    richtig? Als schauen, ob ein Datentyp swappable ist.
    Da ist leider kein Beispiel und das was ich bisher bei Google gefunden habe ging auch nicht (-std=c++17 hatte ich)

    sumpf schrieb:

    Warum tauschst du die Vektoren nicht einfach? Oder nimmst das andere. Oder ähnlich.

    Wie tausche ich die? mit std::swap ging es nicht.



  • Benutzernamer schrieb:

    (Nur wenn ich ohne std swap schreibe geht es)

    Was?



  • manni66 schrieb:

    Benutzernamer schrieb:

    (Nur wenn ich ohne std swap schreibe geht es)

    Was?

    Das meine selbst definierte Funktion verwendet wird



  • Entweder du schreibst verständliche Sätze oder du hilfst dir selbst.



  • Du könntest deiner Klasse move-Konstruktor und move-operator= spendieren.

    A(A &&a) {
            std::cout << "move" << std::endl;
            v.swap(a.v);
        }
    

    (analog für operator=)

    (es fehlt deinem operator= das return *this )

    Übrigens macht std::sort ggf. irgendwelche Optimierungen, wenn die zu sortierende Sequenz kurz ist, sodass dann nicht unbedingt dein spezialisiertes swap aufgerufen wird. Teste doch spaßeshalber mal einen langen zu sorierenden vector. Dann sollte dein spezialisiertes swap auch aufgerufen werden. Move-Konstruktor und Move-Assignment sind dann ggf. in dem Teil hilfreich, wo sort eben nicht dein swap aufruft.



  • wob schrieb:

    Du könntest deiner Klasse move-Konstruktor und move-operator= spendieren.

    A(A &&a) {
            std::cout << "move" << std::endl;
            v.swap(a.v);
        }
    

    (analog für operator=)

    (es fehlt deinem operator= das return *this )

    Übrigens macht std::sort ggf. irgendwelche Optimierungen, wenn die zu sortierende Sequenz kurz ist, sodass dann nicht unbedingt dein spezialisiertes swap aufgerufen wird. Teste doch spaßeshalber mal einen langen zu sorierenden vector. Dann sollte dein spezialisiertes swap auch aufgerufen werden. Move-Konstruktor und Move-Assignment sind dann ggf. in dem Teil hilfreich, wo sort eben nicht dein swap aufruft.

    Vielen Dank, Damit klappt es. Auch bei kurzen Vektor (zumindest in meinem Beispiel). Ist es gewollt das bei dem swapp in "A(A &&a)" a.v undefiniert bzw. leer ist?

    #include <vector>
    #include <iostream>
    #include <algorithm>
    #include <functional>
    class A
    {
    public:
        const int size = 9001;
        std::vector<int> v;
    
        A(){
            std::cout << "cons"<< std::endl;        
            v = std::vector<int>(size);
            v[0] = ((long int)&v[0]); //nur ein wert zum vergleichen
        }
    
        A(const A& a){
            std::cout << "copy"<< std::endl;        
            v.assign(a.v.begin(), a.v.end());
        }
    
        A& operator= (const A& a){
            std::cout << "="<< std::endl;
            v.assign(a.v.begin(), a.v.end());
            return *this;
        }
        A(A &&a) {
            std::cout << "move" << std::endl;
            v.swap(a.v);
    
        }
        A& operator= (A&& a){ //darf kein const hin!?
            std::cout << "=move"<< std::endl;        
            v.swap(a.v);
            return *this;
        }    
    };
    
     struct Acompare
        {        
            Acompare(){}
            bool operator()(const A& a, const A& b) {
                return a.v[0] < b.v[0];
            }
        };
    
    int main(){
    
       A a,b,c,d,e,f,g,h,i;
       std::cout << "init###"<< std::endl;
       std::vector<A> v = std::vector<A> {a,b,c,d,e,f,i};
       std::cout << "sort###"<< std::endl;
       std::sort(v.begin(), v.end(), Acompare() );
       std::cout << "test###"<< std::endl;
       std::cout << a.v[0] << " " << b.v[0] << std::endl;
       std::swap(a,b);
       std::cout << a.v[0] << " " << b.v[0] << std::endl;   
    
       A j(std:move(a));
       //std::cout << a.v[0]; // das geht nicht mehr!
    }
    

    Passt das so oder sollte ich noch etwas anderes machen?



  • Benutzernamer schrieb:

    Vielen Dank, Damit klappt es. Auch bei kurzen Vektor (zumindest in meinem Beispiel). Ist es gewollt das bei dem swapp in "A(A &&a)" a.v undefiniert bzw. leer ist?

    Ähm, eigentlich wollte ich da v = std::move(a.v); geschrieben haben 😉 Aber ein swap tuts da auch. Da ist nix undefiniert.

    Wie gesagt, das ist NICHT dasselbe wie das swap, sondern nur ein weiterer Zusatz. Wenn du entweder das normale swap oder ne Spezialisierung von std::swap gemacht hast, sollte das swap bei großen Kollektionen auch von sort gerufen werden.

    A& operator= (A&& a){ //darf kein const hin!?
    

    Was soll denn dort const sein?



  • wob schrieb:

    Was soll denn dort const sein?

    Das soltle zu gleich eine Aussage sein, dass man da aufpassen muss kein const hinzumachen (bei den Funktionen mit nur einem &) ist ja mit const.

    wob schrieb:

    ne Spezialisierung von std::swap gemacht hast, sollte das swap bei großen Kollektionen auch von sort gerufen werden.

    Allso ich habe mal im Inet gesucht und rumprobiert. Ich habe mein A in namespace geschrieben und eigenes std::swap gemacht mi:

    namespace std {
        void swap(A::A& a, A::A& b)
        {
            A::swap(a, b); // dazu das noch in A definiert
            std::cout<< "swap" << std::endl;     
        }
    }
    //würde man das so machen?
    

    Dies wird bei std::swap aufgerufen, jedoch nicht bei std::sort verwendet, auch bei großen Vektoren nicht. Aber! ich habe die Funktion ohne namespace std Klammern gemacht (und im namespace A außerhalb der Klasse A definiert). Dann verwendet er sie auch bei sort (ab 17 elemente).

    Wenn ich sie genauso in der Klasse schreibe wird sie jedoch nicht verwendet. Ist es möglich sie auf einer anderen Weise in der Klasse zu definieren, damit sie trotzdem verwendet wird?

    und noch als weitere Frage.
    Wenn ich nun eine Klasse B mache, die von A erbt (copy&paste von A und durch B ersetzen + : public A) wird beim swap neben dem move und =move von B auch der Konstruktor von A aufgerufen. Wie umgehe ich das?

    wenn ich z.B. jetzt 17 Elemente in einem B vektor sortiere sieht das so aus:

    =Bswap
    Acons
    Bmove
    =Bmove
    Acons
    Bmove
    =Bmove
    Acons
    Bmove
    =Bmove
    Acons
    Bmove
    =Bmove
    Acons
    ...
    

    (bei einem A Vektor ist das "Acons" nicht mit drin)

    PS: kann man hier auch Code posten in einem Fenster zum scrollen, oder in einem Spoiler, damit nicht die ganze Seite voller Code ist?


  • Mod

    namespace std {
        void swap(A::A& a, A::A& b)
        {
            A::swap(a, b); // dazu das noch in A definiert
            std::cout<< "swap" << std::endl;     
        }
    }
    

    Benutzernamer schrieb:

    würde man das so machen?

    Nein. Das führt zu undefiniertem Verhalten, weil 1. der Standard das so sagt und 2. 2-Phasenlookup diese Überladung nicht finden kann, sofern (wie üblich) die entsprechenen Standardheader bereits vorher inkludiert wurden.
    Eine zulässige Spezialisierung sähe so aus:

    #include <utility>
    namespace std {
        template <> // wichtig !!!
        void swap(A::A& a, A::A& b)
        {
    ...
        }
    }
    

    Benutzernamer schrieb:

    Aber! ich habe die Funktion ohne namespace std Klammern gemacht (und im namespace A außerhalb der Klasse A definiert). Dann verwendet er sie auch bei sort (ab 17 elemente).

    Wenn ich sie genauso in der Klasse schreibe wird sie jedoch nicht verwendet. Ist es möglich sie auf einer anderen Weise in der Klasse zu definieren, damit sie trotzdem verwendet wird?

    Wo die Definition lexikalisch steht, ist irrelevant. Worauf es ankommt, ist dass es keine (statische oder nicht-statische) Memberfunktion ist und durch ADL gefunden werden kann. Kann man überhaupt eine Funktion, die keine Memberfunktion ist, innerhalb einer Klassendefinition definieren? Das geht, wenn man die Funktion als friend deklariert:

    class A
    {
    ...
        friend void swap(A& a, A& b) {
    ...
        }
    };
    

    Benutzernamer schrieb:

    Wenn ich nun eine Klasse B mache, die von A erbt (copy&paste von A und durch B ersetzen + : public A) wird beim swap neben dem move und =move von B auch der Konstruktor von A aufgerufen. Wie umgehe ich das?

    Das ist ja auch richtig. Der Basisklassenanteil muss auch gemoved/geswappt werden. Da swap und move für A schon existieren, solltest du diese bereits existierende Funktionalität nutzen und nicht nocheinmal implementieren.

    class B : public class A { // A swappable
    ...
        B(B&& other) : A(std::move(other)),  // move-ctor von A
            b_member(...)
        {
            ...
        }
    
        B& operator=(B&& rhs)
        {
            A::operator=(std::move(rhs)); // move-assign von A
            ...
            return *this;
        }
    
        friend void swap(B& x, B& y)
        {
            swap(static_cast<A&>(x), static_cast<A&>(y)); // Basisklassenteile von x und y
            ...
        }
    };
    


  • Benutzernamer schrieb:

    wob schrieb:

    ne Spezialisierung von std::swap gemacht hast, sollte das swap bei großen Kollektionen auch von sort gerufen werden.

    Allso ich habe mal im Inet gesucht und rumprobiert. Ich habe mein A in namespace geschrieben und eigenes std::swap gemacht mi:

    namespace std {
        void swap(A::A& a, A::A& b)
        {
            A::swap(a, b); // dazu das noch in A definiert
            std::cout<< "swap" << std::endl;     
        }
    }
    //würde man das so machen?
    

    Nein. Das ist so nicht erlaubt. Du fügst hier im std-Namespace einen Overload hinzu. Nicht gut.
    In std darfst du nur spezialisieren, also mit template<> davor.

    Aber eigentlich ist es am besten, wenn du das swap im selben Namespace wie deine Klasse definierst, da wird es ja auch über ADL gefunden.

    Ich schlage dir das so vor (deine komplizierten Konstruktoren mit push_back-loops habe ich auch mal "etwas" vereinfacht):

    #include <algorithm>
    #include <array>
    #include <iostream>
    #include <vector>
    
    class A {
      public:
        const int size = 9001;
        std::vector<int> v;
        A() : v(size) { std::cout << "cons" << std::endl; }
        A(const A &a) : v(a.v) { std::cout << "copy" << std::endl; }
        A(A &&a) : v(std::move(a.v)) { std::cout << "move" << std::endl; }
    
        A &operator=(const A &a) {
            std::cout << "=" << std::endl;
            v = a.v;
            return *this;
        }
        A &operator=(A &&a) {
            std::cout << "move =" << std::endl;
            v = std::move(a.v);
            return *this;
        }
        friend void swap(A &lhs, A &rhs) {
            std::cout << "my custon swap\n";
            using std::swap;
            swap(lhs.v, rhs.v);
        }
    };
    
    int main() {
        std::array<A, 17> va;
        std::cout << "Sortiere\n";
        va.back().v[0] = -10;
        std::sort(begin(va), end(va),
                  [](const auto &a1, const auto &a2) { return a1.v[0] < a2.v[0]; });
        std::cout << va[0].v[0] << '\n';
    }
    

    Zumindest die STL von g++5.4.0 ruft erst ab Größe 17 swap auf, vorher nicht.

    Edit: in der stl_algo.h findet sich hier:

    /**
       *  @doctodo
       *  This controls some aspect of the sort routines.
      */
      enum { _S_threshold = 16 };
    

    Das ist wohl für das swap ab Größe 17 verantwortlich.



  • Wow, vielen Dank auch beiden. Ich glaub das waren die informativsten Posts die ich seit langen in einen Forum erhalten habe. Ich wollte sogar gerade noch eine Frage stellen. Diese wurde auch schon beantwortet, ohne das ich sie gestellt habe 🙂

    Das mit dem namespace std, ohne template war btw eine Antwort in einem anderen Thread. Gut zu wissen, jedoch wird diese bei sort etc. nicht verwendet.

    Friend hatte ich sogar schon probiert aber da war wohl mein Vektor zu klein. Mit langen geht es!

    komplizierten Konstruktoren mit push_back-loops

    hatte ich schon durch .assign 🙂 Aber deine Methode is besser, wird geändert!

    using std::swap;
            swap(lhs.v, rhs.v);
    

    using heißt: nächste wird auf jeden fall std:swap genommen. Da v Vektoren sind, was macht das für einen Unterschied, ob ich std::swap oder using std::swap schreibe?

    :A(std::move(other)
    

    Das kannte ich noch nicht. Und das wär meine Frage gewesen.
    Sehr nützliche information. Danke!
    Und das muss zwingend nach dem ":" stehen? Wenn es da nicht steht wird der default Konstruktor von A verwendet?

    Ok, nochmal Danke.
    Die Fragen in diesem Post sind nicht ganz so wichtig. Wenn man zuviel weiß, weiß man nur immer mehr das man nichts weiß 🙂


  • Mod

    Benutzernamer schrieb:

    using std::swap;
            swap(lhs.v, rhs.v);
    

    using heißt: nächste wird auf jeden fall std:swap genommen. Da v Vektoren sind, was macht das für einen Unterschied, ob ich std::swap oder using std::swap schreibe?

    In der Regel wird auch std::swap funktionieren, es gibt aber (theoretische - wer so was macht, will Probleme bekommen) Fälle, für die die beiden Varianten nicht die gleiche Funktion aufrufen:

    #include <vector>
    struct foo {};
    void swap(std::vector<foo>&, std::vector<foo>&); // (1)
    
    int main() {
        vector<foo> a,b;
        using std::swap;
        swap(a,b);      // ruft (1) auf
        std::swap(a,b); // ruft swap im Namensraum std auf
    }
    

    Überzeugender ist das, wenn du nicht mit std::vector anfängst, sondern mit irgendeinem fundamentalem Datentyp, wie z.B. int. Der unqualifizierte Aufruf von swap mit int funktioniert nur, wenn dieses vorher per using sichtbar gemacht wurde. Hast du aber auf using verzichtet und gleich std::swap aufgerufen, musst du diesen Code später ändern, falls du bei späterer Programmwartung den Typ änderst auf einen anderen (nicht-std) Typen, der ein spezialisiertes swap hat.

    Du kannst sogar in vielen Fällen einfach nur

    swap(lhs.v, rhs.v);
    

    schreiben, wenn du weist, dass die Argumenttypen ein eigenes swap haben oder aus dem Namensraum std stammen (vorausgesetzt, du bist kein Schlitzohr, und hast nicht irgendwo, wo Namelookup es finden kann, noch ein swap deklarariert, dass keine Funktion und kein Funktionstemplate - also z.B. eine Klasse - ist).

    using std::swap;
    

    wird essentiell, wenn die Argumenttypen noch nicht kennst - also vor allem in Templates.
    using std::swap garantiert in diesen Fällen, dass
    1. das gewöhnliche std::swap gefunden werden kann für Typen, die keigenes swap mitbringen, und
    2. irgendwelche Guerilla-Deklarationen von swap, die keine Funktionen sind, verdeckt werden (und diese somit keinen Schaden anrichtem, indem sie ADL verhindern).

    Meine Empfehlung:
    immer using std::swap + unqualifizierten Aufruf von swap.
    Das funktioniert immer und ist idiomatisch - man muss sich also nicht jedesmal neu darüber Gedanken machen, ob es so richtig ist.


  • Mod

    Benutzernamer schrieb:

    :A(std::move(other)
    

    Das kannte ich noch nicht. Und das wär meine Frage gewesen.
    Sehr nützliche information. Danke!
    Und das muss zwingend nach dem ":" stehen? Wenn es da nicht steht wird der default Konstruktor von A verwendet?

    Jedes B-Objekt enthält ein A-Subobjekt. Wenn also ein B-Objekt erzeugt wird, muss auf irgendeine Weise auch der A-Anteil darin konstruiert werden. Und das geschieht auf natürliche Weise durch einen Konstruktor (dafür sind sie da). Wird kein Konstruktor angegeben, bleibt es defaultmäßig beim Default-Konstruktor...
    Auch der Copy-ctor sollte logischerweise entsprechend den zugehörigen Konstruktor der Basisklasse aufrufen.
    In der Praxis sieht es dann noch ein bisschen anders aus: Es ist eher ungewöhnlich, dass es in mehreren Vererbungsebenen notwendig ist, entsprechend der Regel der Fünf jeweils eigene Copy-/Movectors/Zuweisungsops/Destruktoren zu implementieren. Das deutet auf schlechtes Design hin. In der Regel wird das nur in Ebene erforderlich sein und für alles andere kann man es dem Compiler überlassen, die Implementierungen zu liefern. Diese defaults machen dann bereits das richtige DingTM.



  • Ah, ok das macht Sinn. Aber eine Frage habe ich noch 🙂

    camper schrieb:

    Du kannst sogar in vielen Fällen einfach nur

    swap(lhs.v, rhs.v);
    

    schreiben, wenn du weist, dass die Argumenttypen ein eigenes swap haben oder aus dem Namensraum std stammen

    Wenn ich nun noch eine swap Funktion mit nur einem Argument habe darf ich das using std::swap nicht weglassen, obwohl es für Vektoren eigentlich definiert sein sollte.

    #include <algorithm>
    #include <array>
    #include <iostream>
    #include <vector>
    
    //(normal in einem header)
    class A {
      public:
        const int size = 9001;
        std::vector<int> v;
        A();
        A(const A &a);
        A(A &&a) ;
    
        A &operator=(const A &a) ;
        A &operator=(A &&a) ;
        friend void swap(A &lhs, A &rhs) ;
        void swap(A &lhs);
    };
    
    //(auch in den header? aufteilen?)
    namespace std {
        template <>
        void swap(A& a, A& b)
        {
            std::cout<< "swap" << std::endl; 
            swap(a, b); 
        }
    }
    //(in der .cpp:)
        A::A() : v(size) { std::cout << "cons" << std::endl; }
        A::A(const A &a) : v(a.v) { std::cout << "copy" << std::endl; }
        A::A(A &&a) : v(std::move(a.v)) { std::cout << "move" << std::endl; }
    
        A& A::operator=(const A &a) {
            std::cout << "=" << std::endl;
            v = a.v;
            return *this;
        }
        A& A::operator=(A &&a) {
            std::cout << "move =" << std::endl;
            v = std::move(a.v);
            return *this;
        }
        void swap(A &lhs, A &rhs) {  //ist das die friend Funktion?
            std::cout << "my custon swap\n";
            using std::swap;
            swap(lhs.v, rhs.v);
        }
       void A::swap(A &lhs) {
            std::cout << "my custon swap2\n";
            using std::swap;     //hier kann ich es nicht wegmachen
            swap(v, lhs.v);
        }
    
    int main() {
        std::array<A, 17> va;
        std::cout << "Sortiere\n";
        va.back().v[0] = -10;
        std::sort(begin(va), end(va),
                  [](const auto &a1, const auto &a2) { return a1.v[0] < a2.v[0]; }); 
        std::cout << va[0].v[0] << '\n';
        std::swap(va[1],va[2]);
    }
    


  • auch und müsste ich dann nicht auch das using bei anderen Funktionen verwenden? z.b. std::move, std::sort, std::cout, ...


  • Mod

    Benutzernamer schrieb:

    Ah, ok das macht Sinn. Aber eine Frage habe ich noch 🙂

    camper schrieb:

    Du kannst sogar in vielen Fällen einfach nur

    swap(lhs.v, rhs.v);
    

    schreiben, wenn du weist, dass die Argumenttypen ein eigenes swap haben oder aus dem Namensraum std stammen

    Wenn ich nun noch eine swap Funktion mit nur einem Argument habe darf ich das using std::swap nicht weglassen, obwohl es für Vektoren eigentlich definiert sein sollte.

    void A::swap(A &lhs) {
            std::cout << "my custon swap2\n";
            using std::swap;     //hier kann ich es nicht wegmachen
            swap(v, lhs.v);
        }
    

    Ok, ohne using ist sich hier ist die Funktion selbst im Weg (unqualifiziertes Lookup findet die nicht-statische Memberfunktion, mithin wird swap in this->swap transformiert, und ADL findet nie statt). Das using std::swap dient in diesem Fall nur dazu, den Funktionsnamen zu verdecken. Du könntest z.B.

    void A::swap(A &lhs) {
            std::cout << "my custon swap2\n";
            void swap();
            swap(v, lhs.v);
        }
    

    schreiben. Ist nat. Unfug.

    //(auch in den header? aufteilen?)
    namespace std {
        template <>
        void swap(A& a, A& b)
        {
            std::cout<< "swap" << std::endl; 
            swap(a, b); 
        }
    }
    

    Überflüssig, wenn du sowieso bereits ein normales swap im Namensraum der Klasse hast. Aufteilen oder nur Header bleibt dir überlassen. Wenn alles im Header steht, muss die Funktion ausserdem als inline deklariert werden.

    Benutzernamer schrieb:

    auch und müsste ich dann nicht auch das using bei anderen Funktionen verwenden? z.b. std::move, std::sort, std::cout, ...

    Weder für std::move noch für std::sort ist eine Überladung basierend auf den Argumenttypen vorgesehen - es wäre auch kaum sinnvoll. std::cout ist ein Objekt.


Log in to reply