vendredi 14 janvier 2022

Trying to understand move semantics

I'm studying the 'move semantics' introduced since C++11. I wrote a sample code for creating class objects using normal constructors, a copy constructor, and a move constructor.

/* Useless.cpp */
#include <iostream>

using namespace std;

// interface
class Useless {
private:
    int n; // number of elements
    char* pc; // pointer to data
    static int ct; // number of objects
    void ShowObject() const;
public:
    Useless();
    explicit Useless(int k);
    Useless(int k, char ch);
    Useless(const Useless & f); // regular copy constructor
    Useless(Useless && f); // move constructor
    ~Useless();

    Useless operator+(const Useless & f) const;
    void ShowData() const;
};

int Useless::ct = 0;

Useless::Useless() {
    ++ct;
    n = 0;
    pc =nullptr;
    cout << "default constructor called; number of objects" << ct << endl;
    ShowObject();
}

Useless::Useless(int k) : n(k) {
    ++ct;
    pc = new char[n];
    cout << "int constructor called; number of objects: " << ct << endl;
    ShowObject();
}

Useless::Useless(int k, char ch) : n(k) {
    ++ct;
    pc = new char[n];
    for (int i=0; i< n; i++) {
        pc[i] =  ch;
    }
    cout <<"int, char constructor called; number of objects: " << ct << endl;
    ShowObject();
}

Useless::Useless(const Useless & f) : n(f.n) {
    ++ct;
    pc = new char[n];

    for (int i =0; i< n; i++) {
        pc[i] = f.pc[i]; // deep copy 
    }
    cout << "copy const called; number of objects: " << ct << endl;
    ShowObject();
}

Useless::Useless(Useless && f) : n(f.n) {
    ++ct;
    pc = f.pc; // steal address (shallow copy)
    f.pc = nullptr;
    f.n = 0;
    cout << "move const called; number of objects: " << ct << endl;
    ShowObject();
}

Useless::~Useless() {
    cout << "destructor called; objects left: " << --ct << endl;
    cout << "deleted object:\n";
    ShowObject();
    delete [] pc;
}

Useless Useless::operator+(const Useless & f) const {
    cout << "Entering operator+() \n";
    Useless temp = Useless (n+f.n);

    for (int i=0; i < n; i++) {
        temp.pc[i] = pc[i]; // copy the current object's characters
    }

    for (int i=n; i < temp.n; i++) {
        temp.pc[i] = f.pc[i-n]; // copy the argument object's characters
    }
    cout << "temp object:\n";
    cout <<  "leaving operator+()\n"; 
    return temp;
}

void Useless::ShowObject() const {
    cout << "Number of elements: " << n; 
    cout << "Data address: " << (void *) pc << endl;
}

void Useless::ShowData() const {
    if (n==0) {
        cout << "(object empty)";
    }
    else {
        cout << "data: "; 

        for (int i = 0; i < n; i++) {
            cout << pc[i] ;
        }
    }
    cout << endl;
}

int main() {
    Useless one (10, 'x');
    Useless two = one; // call the copy constructor
    Useless three(20, 'o'); 
    Useless four(one+three); // it should call operator+() and then the move constructor

    cout << "object one: ";
    one.ShowData();
    cout << "object two: ";
    two.ShowData();
    cout << "object three: ";
    three.ShowData();
    cout << "object four: ";
    four.ShowData();
    return 0;
}

I expected the code generates five Useless objects (i.e., object 'one', 'two' 'three', a temporary object made by operator+() function, and the object 'four' created by calling the move constructor). However, the result is different from my expectation. The actual result was as follow:

int, char constructor called; number of objects: 1
Number of elements: 10Data address: 0x56230b195eb0
copy const called; number of objects: 2
Number of elements: 10Data address: 0x56230b1962e0
int, char constructor called; number of objects: 3
Number of elements: 20Data address: 0x56230b196300
Entering operator+() 
int constructor called; number of objects: 4
Number of elements: 30Data address: 0x56230b196320
temp object:
**leaving operator+()**
object one: data: xxxxxxxxxx
object two: data: xxxxxxxxxx
object three: data: oooooooooooooooooooo
object four: data: xxxxxxxxxxoooooooooooooooooooo
destructor called; objects left: 3
deleted object:
Number of elements: 30Data address: 0x56230b196320
destructor called; objects left: 2
deleted object:
Number of elements: 20Data address: 0x56230b196300
destructor called; objects left: 1
deleted object:
Number of elements: 10Data address: 0x56230b1962e0
destructor called; objects left: 0
deleted object:
Number of elements: 10Data address: 0x56230b195eb0

My expectation was the program generates the fifth object (object 'four') by calling the move constructor defined above (with the result of 'one + three' as input argument). I would appreciate any clue for understanding the result.

Aucun commentaire:

Enregistrer un commentaire