CodeGuru Home VC++ / MFC / C++ .NET / C# Visual Basic VB Forums Developer.com
Results 1 to 10 of 10
  1. #1
    Join Date
    Jun 2015
    Posts
    175

    Rvalue reference and move semantics

    Hi all,

    I'm trying to study rvalue reference and move semantics using this tut. In the How it works section of that page, it says: Both push_back() calls resolve as push_back(T&&) because their arguments are rvalues.

    What I can think of is that:
    Code:
    std::vector<A> v; 
    v.push_back(A(25));
    will create a vector of type A named v. Then a temporary object of type A is created with the value 25. Since it's a new object, although it's temporary, it should call the constructor of the class. Right until here, please?

    Then it needs to go into the vector. The temporary object will then be interpreted as an rvalue reference and subsequently the move constructor will be called. But how!? I mean how the compiler knows that here it must call the move constructor?
    And is it true that an rvalue reference, say (T&& t), will accept only the address of a temporary object?

    Thanks in advance.

  2. #2
    2kaud's Avatar
    2kaud is offline Super Moderator Power Poster
    Join Date
    Dec 2012
    Location
    England
    Posts
    7,825

    Re: Rvalue reference and move semantics

    will create a vector of type A named v.
    Yes.

    Then a temporary object of type A is created with the value 25. Since it's a new object, although it's temporary, it should call the constructor of the class.
    Yes - if A is of a type that has a constructor that can take 25 as a parameter.

    The temporary object will then be interpreted as an rvalue reference
    Yes.

    subsequently the move constructor will be called
    No. The .push_back() member function for a ref ref (&&) will be called if one is defined. This is not a constructor.

    how the compiler knows that here it must call the move constructor
    if the object is an r-value and a ref ref (&&) function is defined then the ref ref function is used. If no ref ref is defined, then it tries to match to another function definition (const ref (const &) or a value).

    rvalue reference, say (T&& t), will accept only the address of a temporary object?
    No. Addresses are not directly a part of a ref ref (ignoring what's happening behind the scenes). A ref ref only takes an rvalue reference (usually temporary object). Note that you can't have a const &&!
    All advice is offered in good faith only. All my code is tested (unless stated explicitly otherwise) with the latest version of Microsoft Visual Studio (using the supported features of the latest standard) and is offered as examples only - not as production quality. I cannot offer advice regarding any other c/c++ compiler/IDE or incompatibilities with VS. You are ultimately responsible for the effects of your programs and the integrity of the machines they run on. Anything I post, code snippets, advice, etc is licensed as Public Domain https://creativecommons.org/publicdomain/zero/1.0/ and can be used without reference or acknowledgement. Also note that I only provide advice and guidance via the forums - and not via private messages!

    C++23 Compiler: Microsoft VS2022 (17.6.5)

  3. #3
    Join Date
    Jun 2015
    Posts
    175

    Re: Rvalue reference and move semantics

    Thanks so much dear 2Kaud for your nice explanation.

    The .push_back() member function for a ref ref (&&) will be called if one is defined.
    I tested the first push: v.push_back(A(25));. On this line, firstly the class's constructor is called, for the temp object, and then the class's move constructor (to move the object into the vector). So seemingly it's not the move constructor of the std vector which is called. I'm not sure if the vector doesn't have that move constructor, but when there's one in the class, the compiler prefers it to the built-in move constructor of the std vector, apparently.


    A ref ref only takes an rvalue reference (usually temporary object).
    Apart from a temporary object, what else can a ref ref take, please? What other thing than a temporary object is usual for a ref ref to take as well?
    Last edited by tomy12; April 13th, 2020 at 04:13 PM.

  4. #4
    2kaud's Avatar
    2kaud is offline Super Moderator Power Poster
    Join Date
    Dec 2012
    Location
    England
    Posts
    7,825

    Re: Rvalue reference and move semantics

    On this line, firstly the class's constructor is called, for the temp object, and then the class's move constructor (to move the object into the vector).
    With the code:

    Code:
    v.push_back(A(25));
    The move constructor for A should not be called. The explicit constructor for size_t should be called.

    So seemingly it's not the move constructor of the std vector which is called.
    As I explained in my post #2,

    Code:
    v.push_back(A(25));
    This calls the ref ref variant of .push_back()

    Code:
    void push_back (value_type&& val);
    as A(25) is an r-value. This is not a constructor. A constructor is when a new instance of a class is created. .push_back() doesn't create a new instance - so isn't a constructor.


    Apart from a temporary object, what else can a ref ref take, please? What other thing than a temporary object is usual for a ref ref to take as well?
    You can make an l-value into an r-value by using std::move(). But you have to be careful, as a function with a && argument usually changes the contents of the called parameter (which is why the parameter is && and not const &&).
    All advice is offered in good faith only. All my code is tested (unless stated explicitly otherwise) with the latest version of Microsoft Visual Studio (using the supported features of the latest standard) and is offered as examples only - not as production quality. I cannot offer advice regarding any other c/c++ compiler/IDE or incompatibilities with VS. You are ultimately responsible for the effects of your programs and the integrity of the machines they run on. Anything I post, code snippets, advice, etc is licensed as Public Domain https://creativecommons.org/publicdomain/zero/1.0/ and can be used without reference or acknowledgement. Also note that I only provide advice and guidance via the forums - and not via private messages!

    C++23 Compiler: Microsoft VS2022 (17.6.5)

  5. #5
    Join Date
    Jun 2015
    Posts
    175

    Re: Rvalue reference and move semantics

    When I run the code by F10 on VS 2019, I see that all of the following three A's member functions are called when v.push_back(A(25)); is done:

    1) constructor, on line 7
    2) move constructor on line 67
    3) And the destructor

    The output for that single push-back:
    A(size_t). length = 25.
    A(A&&). length = 25. Moving resource.
    ~A(). length = 0.


    So if my understanding is correct, it's A's move constructor which is called not the vector's!
    Here is the complete code.

    Code:
    using namespace std;
    
    class A
    {
    public:
    
        explicit A(size_t length) : mLength(length), mData(new int[length])
        {
            cout << "A(size_t). length = "
                << mLength << "." << endl;
        }
    
        // Destructor
        ~A()
        {
            cout << "~A(). length = " << mLength << ".";
    
            if (mData != nullptr) {
                cout << "Deleting resource.";
                delete[] mData;  // Delete the resource.
            }
    
            cout << endl;
        }
    
        // Copy constructor.
        A(const A& other) : mLength(other.mLength), mData(new int[other.mLength])
        {
            cout << "A(const A&). length = "
                << other.mLength << ". Copying resource." << endl;
    
            copy(other.mData, other.mData + mLength, mData);
        }
    
        // Copy assignment operator.
        A& operator=(const A& other)
        {
            cout << "operator=(const A&). length = "
                << other.mLength << ". Copying resource." << endl;
    
            if (this != &other) {
                delete[] mData;  // Free the existing resource.
                mLength = other.mLength;
                mData = new int[mLength];
                copy(other.mData, other.mData + mLength, mData);
            }
            return *this;
        }
    
        // Move constructor.
        A(A&& other) : mData(nullptr), mLength(0)
        {
            cout << "A(A&&). length = "
                << other.mLength << ". Moving resource.\n";
    
            // Copy the data pointer and its length from the source object.
            mData = other.mData;
            mLength = other.mLength;
    
            // Release the data pointer from the source object so that
            // the destructor does not free the memory multiple times.
            other.mData = nullptr;
            other.mLength = 0;
        }
    
        // Move assignment operator.
        A& operator=(A&& other) 
        {
            cout << "operator=(A&&). length = "
                << other.mLength << ". Move assignment" << endl;
    
            if (this != &other) {
                
                delete[] mData; // Free the existing resource.
    
                // Copy the data pointer and its length from the 
                // source object.
                mData = other.mData;
                mLength = other.mLength;
    
                // Release the data pointer from the source object so that
                // the destructor does not free the memory multiple times.
                other.mData = nullptr;
                other.mLength = 0;
            }
            return *this;
        }
    
        // Retrieves the length of the data resource.
        size_t Length() const
        {
            return mLength;
        }
    
    private:
        size_t mLength; // The length of the resource.
        int* mData;     // The resource.
    };
    
    #include <vector>
    
    int main()
    {
        vector<A> v;
        v.push_back(A(25));
        v.push_back(A(75));
    
        // Insert a new element into the second position of the vector.
        v.insert(v.begin() + 1, A(50));
    
        cin.get();
        return 0;
    }
    Am I wrong, please?
    Last edited by tomy12; April 14th, 2020 at 05:15 AM.

  6. #6
    2kaud's Avatar
    2kaud is offline Super Moderator Power Poster
    Join Date
    Dec 2012
    Location
    England
    Posts
    7,825

    Re: Rvalue reference and move semantics

    If you just consider:

    Code:
    v.push_back(A(25));
    then you get the output:

    Code:
    A(size_t). length = 25.
    A(A&&). length = 25. Moving resource.
    ~A(). length = 0.
    ~A(). length = 25.Deleting resource.
    The size_t constructor is for the A(25)

    The A&& constructor (move) is called by .push_ back() when it is inserting the item into the vector.

    The ~A() 0 is the destructor called after the .push_back() and is the destructor for the A(25) constructor - but because of move semantics the size is 0 and not 25.

    The ~A() 25 is the destructor called from vector A destructor and is the effective destructor for the A(25) constructor.

    The A move constructor is not invoked directly by the program, but indirectly via member functions of the vector class. This is why move constructor (and move assignment) should always be provided when appropriate (usually when dealing with dynamic memory) because although your program may not call these directly, other 'elements' of the program could well. If a move isn't provided, then the copy one will be used.

    Note that your copy assignment and move assignment are not IMO best practice. delete [] should only be part of the destructor and copy() should only be part of the copy constructor. I would have expected a .swap() member function and also a default constructor. Then copy assignment, move assignment and move constructor are implemented in terms of existing member functions. So if the class is changed, then you know that you only have 3 places to change - copy constructor, destructor and swap().
    All advice is offered in good faith only. All my code is tested (unless stated explicitly otherwise) with the latest version of Microsoft Visual Studio (using the supported features of the latest standard) and is offered as examples only - not as production quality. I cannot offer advice regarding any other c/c++ compiler/IDE or incompatibilities with VS. You are ultimately responsible for the effects of your programs and the integrity of the machines they run on. Anything I post, code snippets, advice, etc is licensed as Public Domain https://creativecommons.org/publicdomain/zero/1.0/ and can be used without reference or acknowledgement. Also note that I only provide advice and guidance via the forums - and not via private messages!

    C++23 Compiler: Microsoft VS2022 (17.6.5)

  7. #7
    Join Date
    Jun 2015
    Posts
    175

    Re: Rvalue reference and move semantics

    Quote Originally Posted by 2kaud View Post
    If you just consider:

    Code:
    v.push_back(A(25));
    then you get the output:

    Code:
    A(size_t). length = 25.
    A(A&&). length = 25. Moving resource.
    ~A(). length = 0.
    ~A(). length = 25.Deleting resource.
    The size_t constructor is for the A(25)
    When I use main() this way:

    Code:
    vector<A> v;
        v.push_back(A(25));
       // v.push_back(A(75));
    
        // Insert a new element into the second position of the vector.
       // v.insert(v.begin() + 1, A(50));
    I get only this output, nothing more:
    A(size_t). length = 25.
    A(A&&). length = 25. Moving resource.
    ~A(). length = 0.


    Is my compiler, VS 2019, working as expected?

    The A&& constructor (move) is called by .push_ back() when it is inserting the item into the vector.
    I think I also said that.

    The ~A() 0 is the destructor called after the .push_back() and is the destructor for the A(25) constructor - but because of move semantics the size is 0 and not 25.
    Yes, I understand.

    The ~A() 25 is the destructor called from vector A destructor and is the effective destructor for the A(25) constructor.
    This didn't happen for me.

    The A move constructor is not invoked directly by the program, but indirectly via member functions of the vector class. This is why move constructor (and move assignment) should always be provided when appropriate (usually when dealing with dynamic memory) because although your program may not call these directly, other 'elements' of the program could well. If a move isn't provided, then the copy one will be used.
    I understand this too. Thanks

    Note that your copy assignment and move assignment are not IMO best practice. delete [] should only be part of the destructor and copy() should only be part of the copy constructor. I would have expected a .swap() member function and also a default constructor. Then copy assignment, move assignment and move constructor are implemented in terms of existing member functions. So if the class is changed, then you know that you only have 3 places to change - copy constructor, destructor and swap().
    Things are getting murky. We will consider this later on, if you agree.
    Last edited by tomy12; April 15th, 2020 at 03:07 AM.

  8. #8
    2kaud's Avatar
    2kaud is offline Super Moderator Power Poster
    Join Date
    Dec 2012
    Location
    England
    Posts
    7,825

    Re: Rvalue reference and move semantics

    I get only this output, nothing more:
    A(size_t). length = 25.
    A(A&&). length = 25. Moving resource.
    ~A(). length = 0.

    Is my compiler, VS 2019, working as expected?
    I use VS2019 as well. Note that the ~A(). length = 25.Deleting resource. message is displayed after main() exits (when the vector destructor is called) - so after your cin.get() statement.

    We will consider this later on, if you agree.
    Of course.
    All advice is offered in good faith only. All my code is tested (unless stated explicitly otherwise) with the latest version of Microsoft Visual Studio (using the supported features of the latest standard) and is offered as examples only - not as production quality. I cannot offer advice regarding any other c/c++ compiler/IDE or incompatibilities with VS. You are ultimately responsible for the effects of your programs and the integrity of the machines they run on. Anything I post, code snippets, advice, etc is licensed as Public Domain https://creativecommons.org/publicdomain/zero/1.0/ and can be used without reference or acknowledgement. Also note that I only provide advice and guidance via the forums - and not via private messages!

    C++23 Compiler: Microsoft VS2022 (17.6.5)

  9. #9
    Join Date
    Jun 2015
    Posts
    175

    Re: Rvalue reference and move semantics

    If we still consider v.push_back(A(25)); only, in the move constructor, we have:
    Code:
    // Move constructor.
        A(A&& other) : mData(nullptr), mLength(0)
    What are those mData and mLength which are freed/zeroed? I think they're members of the first item in the vector which has been a type of A now. They are not of our temporary object because our temporary object here is other , which is going into the vector and then at the end of the move constructor are freed/zeroed, this way:
    Code:
    other.mData = nullptr;
            other.mLength = 0;
    Agree, please?

    If yes, the first item of the vector is empty, the vector still have nothing, then why do we need to do those two actions?
    Last edited by tomy12; April 15th, 2020 at 05:35 AM.

  10. #10
    2kaud's Avatar
    2kaud is offline Super Moderator Power Poster
    Join Date
    Dec 2012
    Location
    England
    Posts
    7,825

    Re: Rvalue reference and move semantics

    The move constructor is not well coded. A better coding without using swap would be:

    Code:
    	// Move constructor.
    	A(A&& other) : mData(other.mData), mLength(other.mLength)
    	{
    		cout << "A(A&&). length = "
    			<< other.mLength << ". Moving resource.\n";
    
    		// Release the data pointer from the source object so that
    		// the destructor does not free the memory multiple times.
    		other.mData = nullptr;
    		other.mLength = 0;
    	}
    What the move constructor does is to set the new object data elements to the same as that it is being constructed from, then set the data elements of the source object (being created from) to nullptr/0 so that when that object's destructor (source) is called then nothing happens so the memory isn't released as it's now used by the new object.
    All advice is offered in good faith only. All my code is tested (unless stated explicitly otherwise) with the latest version of Microsoft Visual Studio (using the supported features of the latest standard) and is offered as examples only - not as production quality. I cannot offer advice regarding any other c/c++ compiler/IDE or incompatibilities with VS. You are ultimately responsible for the effects of your programs and the integrity of the machines they run on. Anything I post, code snippets, advice, etc is licensed as Public Domain https://creativecommons.org/publicdomain/zero/1.0/ and can be used without reference or acknowledgement. Also note that I only provide advice and guidance via the forums - and not via private messages!

    C++23 Compiler: Microsoft VS2022 (17.6.5)

Tags for this Thread

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •  





Click Here to Expand Forum to Full Width

Featured