Nicht-kopierbare Klasse mit move Semantik



  • Huhu,

    ich stehe grade etwas aufm Schlauch und kriege eine Funktion einfach nicht umgesetzt. Aber vielleicht geht das auch überhaupt nicht, was ich mir da vorstelle.
    Ich möchte eine nicht-kopierbare Klasse haben, die einmal per move-Semantik zugewiesen werden kann um damit zu arbeiten. Sie soll danach weder kopierbar noch zuweisbar sein, wenigstens das funktioniert aber;)

    #include <utility>
    
    class Test
    {
       unsigned int* val_;
       unsigned int  count_;
    
    public:
       Test() :
          val_( nullptr ),
          count_( 0 )
       {
       }
    
       explicit Test( unsigned int& v ) :
          val_( &v ),
          count_( 0 )
       {
          inc();
       }
    
       ~Test()
       {
          dec();
       }
    
       Test( const Test& )	= delete;
       Test& operator=( const Test& ) = delete;
    
       Test( Test&& other )
       {
          val_ = other.val_;
          count_ = other.count_;
          other.val_ = nullptr;
          other.count_ = 0;
       }
    
       Test& operator=( Test&& other )
       {
          val_ = other.val_;
          count_ = other.count_;
    
          other.val_ = nullptr;
          other.count_ = 0;
          return *this;
       }
    
       void release()
       {
          dec();
       }
    private:
       void inc()
       {
          if( val_ )
          {
             ++(*val_);
             ++count_;
          }
       }
    
       void dec()
       {
          if( val_ )
          {
             --(*val_);
             --count_;
          }
       }
    };
    
    Test&& make_test( unsigned int& v )
    {
       return std::move<Test>( Test( v ) );
    }
    
    int main()
    {
       unsigned int z = 5;
       auto t1 = make_test( z ); 
       // hier hätte ich gern, dass t1.val_6 und t1.count_ 1 ist,
       // aber sie sind 5 und 0
    
       t1.release();
       // hier hätte ich gern, dass t1.val_5 und t1.count_ 0 ist
    
       auto t2 = make_test( z ); // ok
       t3 = t1; // Compiler Fehler, ok
       t2 = t;  // Compiler Fehler, ok
    
    }
    

    Der Plan ist, RAII Unterstützung für andere Klassen zu bauen, die Test Klasse ist lediglich zur Anschauung. Wie kriegt man so etwas hin? Kriegt man so etwas hin?



  • Dein make_test ist auf jeden Fall verkehrt.

    Ich weiß nicht so recht, was du genau mit der Referenz auf die temporäre Variable erreichen willst, aber das geht so nicht und ist UB.

    Warum nicht einfach so:

    Test make_test( unsigned int& v )
    {
       return Test( v );
    }
    


  • wob schrieb:

    ...
    Warum nicht einfach so:

    Test make_test( unsigned int& v )
    {
       return Test( v );
    }
    

    Weil ich kaum C++11 mache und die neuen Referenztypen nicht ganz verstanden habe 😉
    Leider fehlt mir aktuell auch die Zeit, mich damit zu beschäftigen, auch wenn´s dringend nötig wäre.

    Aber danke für die Hilfe, damit funktioniert´s.



  • DocShoe schrieb:

    Weil ich kaum C++11 mache und die neuen Referenztypen nicht ganz verstanden habe

    Test&& ist nichts Magisches, nur ein Referenz-Typ. Dass man von einer Funktion keine Referenz auf funktionslokales Objekte zurück gibt, weil es ja nicht mehr existieren würde nachdem die Ausführung mit der Funktion fertig ist, gilt für alle Referenzen.

    Der Unterschied zwischen int& und int&& liegt nur darin, wie sich diese Referenzen initialisieren lassen und bei der Überladungsauflösung:

    void lvalue(int&);
    void rvalue(int&&);
    
    void test_overload(const int&);  // #1
    void test_overload(int&&);       // #2
    
    int main() {
        int lve = 32;
        lvalue(lve); // OK, lve ist ein lvalue Ausdruck
        lvalue(42);  // Illegal, kompiliert nicht
        rvalue(lve); // Illegal, kompiliert nicht
        rvalue(42);  // OK, 42 ist ein rvalue Ausdruck
    
        test_overload(lve);            // OK. Viable: { #1 },     resolve to #1
        test_overload(42);             // OK. Viable: { #1, #2 }, resolve to #2
        test_overload(std::move(lve)); // OK. Viable: { #1, #2 }, resolve to #2
    }
    

    In den letzten beiden Fällen sind beide Überladungen "viable" (weil bei #1 noch ein const dabei steht) aber die Überladung wird zugunsten von #2 aufgelöst. #2 ist ein besserer Match.

    Diese Art der Überladung findet üblicherweise bei Konstruktoren statt (Copy-Constructor + Move-Constructor). Was bei einem Move dann genau passiert, das definiert ja dann der Klassen-Autor über den Move-Konstruktor selbst.

    Die Idee ist nur, dass man in einer Funktion, die ein Ding&& entgegen nimmt, mit dem Ding alles machen kann, ohne dass sich der Aufrufer beschweren kann; denn entweder bezieht sich die Referenz auf ein temporäres Objekt, auf das kein anderer mehr Zugriff hat, oder der Aufrufer hat explizit per std::move gesagt, dass er kein Interesse mehr an dem aktuellen Zustand des Objektes hat.


Anmelden zum Antworten