0

I've started to learn C++ but I have some doubts about the move semantics. When the book says that the move constructor of A "steals" the attributes of an rvalue B, is it means that the pointer to a value V switch from B to A? So in this way, A doesn't need to allocate another portion of memory?

If so, why in this code

class CBuffer{
private:
    int size;
    int csize;
    char* ptr;
    char rtp;
public:
    CBuffer(int size): size(size), csize(0){
        ptr = new char[size];
        *ptr = 'a';
    }

    CBuffer(const CBuffer& source): size(3), csize(source.csize) {
        this->ptr = new char[size];
        memcpy(this->ptr, source.ptr, size);
    }

    CBuffer(CBuffer&& source){
        this->ptr = source.ptr;
        this->size = source.size;
        std::cout << &this->ptr << std::endl;
        std::cout << &source.ptr << std::endl;
        source.ptr = nullptr;
    }

};



int main(){
    CBuffer a = CBuffer(1);
    CBuffer b = std::move(a);
    CBuffer c = b;

the lines

std::cout << &this->ptr << std::endl;
std::cout << &source.ptr << std::endl;

print different addresses?

Lorenzoi
  • 21
  • 7

1 Answers1

1

The move semantics means that instead of creating a copy of another object, we allow the object to take any dynamically allocated memory inside of this other object.

We do so by swapping any dynamically allocated memory between the two, meaning, that the value (meaning, the memory address that is stored in the pointer) of the pointer in the first object will be now the value of the pointer in the new object, and we put a nullptr in the rvalue reference instead, so when it is destroyed, it will destroy it as well.

I have added a few things to the code:

  1. I have initialized all of the variables in the initializer list. Also, I have removed the hard-coded initialization of size in the copy-ctor.
  2. It is common to use the name other for the other object in copy/move operations.
  3. An Error: you must put a value inside of the ptr, it is a bad practice not initializing your variables. Actually, it makes an invalid delete if we add a dtor, and use the swap idiom here (explained below).
  4. Last but not least, the move ctor can be called on a live object, and instead of deleting the current ptr, you put nullptr on other, which means that is never deleted. for example, if main was:
int main()
{
    CBuffer a{1};
    CBuffer b{2};
    a = std::move(b); // legal, but causes memory leak in your code, the value of a.ptr is never deleted!
}

How does the swap idiom helps us?!

Let's look at my example; When we swap the values (meaning a.ptr = old b.ptr and b.ptr = old a.ptr), whenever b is deleted, the "old" a.ptr, which now resides in b will be deleted, since we just swapped them. Usually, when we use the move semantics, the rvalue is going to be destroyed soon, and we just "ride" on its destruction in order to prevent memory leaks.

Your code should look like that (added some prints for better understanding):

class CBuffer{
private:
    int size = 0;
    int csize = 0;
    char* ptr = nullptr; // very important!!!
    char rtp = 0;
    int id = 0;

public:
    static int get_current_count() 
    { 
        static int object_cnt = 1;
        return object_cnt++; 
    }

    CBuffer(int size) : 
        size(size), 
        csize(0),
        ptr(new char[size]),
        rtp(0)
    {
        id = get_current_count();
        std::cout << "On ctor of " << id << std::endl;
        ptr[0] = 'a';
    }

    CBuffer(const CBuffer& other) : 
        size(other.size), 
        csize(other.csize), 
        ptr(new char[size]),
        id(get_current_count())
    {
        std::cout << "On copy ctor of " << other.id << " to " << this->id << std::endl;
        std::memcpy(this->ptr, other.ptr, size);
    }

    CBuffer(CBuffer&& other) :
        size(other.size),
        csize(other.csize),
        rtp(other.rtp), 
        id(get_current_count())
    {
        std::cout << "On move ctor of " << other.id << " to " << this->id << std::endl;
        std::swap(this->ptr, other.ptr);
    }

    ~CBuffer()
    {
        std::cout << "On dtor of " << id << std::endl;
        delete[] ptr;
    }
};

int main()
{
    CBuffer a = CBuffer(1);
    CBuffer b = std::move(a);
    CBuffer c = b;
}

And the output is:

On ctor of 1
On move ctor of 1 to 2
On copy ctor of 2 to 3
On dtor of 3
On dtor of 2
On dtor of 1
Kerek
  • 1,065
  • 7
  • 17
  • 1
    I think `source` or `src` is also a quite common name for that purpose, and is actually descriptive of its meaning. – aschepler Mar 29 '20 at 15:11
  • I have encountered only other, and it is also like that in [cpp reference](https://en.cppreference.com/w/cpp/language/move_assignment). Anyhow, noted to myself that some use source. – Kerek Mar 29 '20 at 15:42