Home > OS >  Trying to understand move semantics
Trying to understand move semantics

Time:01-23

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.

CodePudding user response:

That's copy elision and return value optimization in action. See copy elision.

In gcc/clang you can disable it using -fno-elide-constructors and then you'll get your 5th object.

  •  Tags:  
  • Related