C++: Move constructor not called, unless explicit casting is called

77 Views Asked by At

I created a class, "ListElement", that contains as a single data variable an instance of the class "MyClass" which contains a dynamically allocated array.

  • For ListElement I created a default constructor which delegates to a right-value constructor that initializes an instance of MyClass.

  • For MyClass I created a constructor which initializes its array to an inputted value (and a default constructor which delegates to the one above with value 0). Furthermore, I included a copy and a move constructor.

I'm, however, mostly confused, why the move constructor is not called unless the datatype is casted as an r-value reference explicitly, although it is already passed as such (see below).

#include <iostream>

template <int arrSize>
class MyClass {
private:
  // Data
  int* a;

public:
  //Constructor
  MyClass(int value){
    this->a = new int[arrSize];
    for(unsigned int i = 0; i<arrSize; ++i){
      a[i] = value;
    }
    std::cout << "MyClass created!" << std::endl;
  }
  MyClass(): MyClass(0){}
  //Copy  Constructor
  MyClass(const MyClass<arrSize>& other){
    this->a = new int[arrSize];
    for(unsigned int i = 0; i<arrSize;++i){
      a[i] = other.a[i];
    }
    std::cout << "MyClass copied!" << std:: endl;
  }
  //Move Constructor
  MyClass(MyClass<arrSize>&& other){
    this->a = other.a;
    other.a = nullptr;
    std::cout << "MyClass moved!" << std::endl;
  }
  //Destructor
  ~MyClass(){
    delete[] this->a;
    std::cout << "MyClass deleted!" << std::endl;
  }
};

class ListElement {
public:
  //Constructors
  ListElement(MyClass<5>&& data) : data((MyClass<5>&&)data) {} // QUESTION 
  ListElement(): ListElement(MyClass<5>(1)){}

private:
  //Data
  MyClass<5> data;
};

int main() {
  ListElement myZeroListElement{};
  return 0;
}

Question: Why is the copy constructor called if I write for this line (see comments)?

ListElement(MyClass<5>&& data) : data(data) {}

"data" is a right-value reference, but the move constructor is only called when I explecitly cast it, so-to-say, again into a right-value reference like this:

ListElement(MyClass<5>&& data) : data((MyClass<5>&&)data) {}

GREATLY APPRECIATED!

1

There are 1 best solutions below

0
On BEST ANSWER

Question 1

Because data is not an rvalue. Rvalues are nameless. data is a name ⇒ not an rvalue. Named rvalue references are references that bind to rvalues but they are not rvalues themselves.

Your cast is correct but not generic and not idiomatic. The standard library offers std::move precisely for this purpose, use it.

Question 2

I would argue that it should be initialized in the initialisation list to the pointer to allocated data. Everything should be initialised in the initialisation list. If this is impossible then probably there's a problem with your design.

MyClass(int value): a(new int[arrSize]) { ... }

Of course in real code you should be using std::vector or std::array.

Question 3

The size is not defined dynamically. It must be known at compile time.