What happen when a lvalue assigned to a rvalue reference? No destruction of the temporary object?

244 Views Asked by At
#include <iostream>
using namespace std;
#include <cstring>

class Word{
    private:
        char* ptr = nullptr;
    public:
        Word(){
            cout << "default constructor" << endl;
        }
        Word(const char* sentence):ptr{new char [strlen(sentence)+1]}{
            strcpy(ptr, sentence);
            cout << "conversion constructor: " << ptr << endl;
        }
        Word(const Word& w):ptr{new char [strlen(w.ptr)+1]}{
            strcpy(ptr, w.ptr);
            cout << "copy constructor: "<< ptr << endl;
        }
        ~Word(){
            cout << "destructor: " << ptr << endl;
        }
};

int main(){
    Word a ("A stands for apple!");
    Word&& b = "B stands for Banana, rvalue ref";
    b = a;
}

My Eclipse result:

conversion constructor: A stands for apple!
conversion constructor: B stands for Banana, rvalue ref
destructor: A stands for apple!
destructor: A stands for apple!

My Extectation:

conversion constructor: A stands for apple!
conversion constructor: B stands for Banana, rvalue ref
destructor: B stands for Banana, rvalue ref
destructor: A stands for apple!
destructor: A stands for apple!

I am confused by this step.

b = a;

When a is assigned to b, it could suppose to first destruct the temporary object (with cstring of "B stands for Banana, rvalue ref") that b is holding, then assign the value of a to b. Why in the Eclipse's result, it does not perform the destruction of the temporary object?

2

There are 2 best solutions below

21
Stephen Newell On

b = a; is invoking the assignment operator, not the copy constructor. If explicitly deleted, the code won't compile:

// trimmed...
Word(const Word& w):ptr{new char [strlen(w.ptr)+1]}{
    strcpy(ptr, w.ptr);
    cout << "copy constructor: "<< ptr << endl;
}
Word& operator = (const Word& w) = delete;

The compilation line:

$ g++ rvalue-ref.cpp -o rvalue-ref
rvalue-ref.cpp: In function ‘int main()’:
rvalue-ref.cpp:45:9: error: use of deleted function ‘Word& Word::operator=(const Word&)’
     b = a;
         ^
rvalue-ref.cpp:20:15: note: declared here
         Word& operator = (const Word& w) = delete;
               ^~~~~~~~

The compiler will provide a default assignment operator, so the code in your question is leverage that. To see what's happening, add copy-assignment and move-assignment operators.

Word& operator = (const Word& w) {
    auto temp = new char [strlen(w.ptr)+1];
    strcpy(temp, w.ptr);
    delete [] ptr;
    ptr = temp;
    cout << "assignment operator: " << ptr << endl;
    return *this;
}
Word& operator = (Word&& w) {
    std::swap(ptr, w.ptr);
    cout << "swap operator: " << ptr << endl;
    return *this;
}

With these in place, I get the expected output:

conversion constructor: A stands for apple!
conversion constructor: B stands for Banana, rvalue ref
assignment operator: A stands for apple!
destructor: A stands for apple!
destructor: A stands for apple!

As an aside, you're leaking memory. Your destructor should look like this:

~Word(){
    cout << "destructor: " << ptr << endl;
    delete [] ptr;
}

$ valgrind ./rvalue-ref

==10736== Memcheck, a memory error detector
==10736== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==10736== Using Valgrind-3.13.0 and LibVEX; rerun with -h for copyright info
==10736== Command: ./rvalue-ref
==10736== 
conversion constructor: A stands for apple!
conversion constructor: B stands for Banana, rvalue ref
assignment operator: A stands for apple!
destructor: A stands for apple!
destructor: A stands for apple!
==10736== 
==10736== HEAP SUMMARY:
==10736==     in use at exit: 0 bytes in 0 blocks
==10736==   total heap usage: 5 allocs, 5 frees, 73,800 bytes allocated
==10736== 
==10736== All heap blocks were freed -- no leaks are possible
==10736== 
==10736== For counts of detected and suppressed errors, rerun with: -v
==10736== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

You could also implement the assignment operator using the copy/swap idiom (below). This will add an extra constructor/destructor output due to the temporary, but it's good practice overall.

Word& operator = (const Word& w) {
    Word temp(w);
    std::swap(ptr, temp.ptr);
    cout << "assignment operator: " << ptr << endl;
    return *this;
}
0
eerorika On

Your expectation is wrong. There can't be more destructions than there are constructions.

When a is assigned to b, it could suppose to first destruct the temporary object

No. b refers to the temporary object. Assigning to an object doesn't cause the object to be destroyed.

What happens is: The implicitly generated assignment operator of Word will assign all members. So, after the assignment the previous value of b.ptr is leaked and has the same value (points to same string) as a.ptr.