0

I'm trying to invoke the move constructor in the code below, where the addition of two lists occurs. Given what I read in the Stroustrup book, the move constructor should be invoked when operator+(const X& a, const X& b) returns, however it does not per the output below. Only the move assignment is being invoked.

Does anyone know why the move constructor is not being invoked on return from function?

Thanks

#include <iostream>
#include <list>
using std::list;
using std::cout;
using std::initializer_list;

class X {
  list<int> *p;
  size_t sz;
public:
  X() : p{ new list<int>(0) } { }
  X(size_t size) : p{ new list<int>(size) }, sz {size} { }
  X(initializer_list<int> li) : p{ new list<int>(li) }, sz { li.size() } { }

  X(const X&);
  X& operator=(const X&);

  // move
  X(X&&);
  X& operator=(X&&);

  size_t size() const { return sz; }
  int &operator[](int);
  int &operator[](int) const;

  class size_mismatch { };
};

X::X(const X& a)
{
  p = new list<int>();
  for (auto pp : *(a.p))
    p->push_back(pp);
  sz = a.sz;
}

X& X::operator=(const X& a)
{
  delete p;
  p = new list<int>();
  for (auto pp : *(a.p))
    p->push_back(pp);
  sz = a.sz;
  return *this;
}

X::X(X&& a) : p{ a.p }, sz{ a.sz }
{
  cout << "here0\n";
  a.p = nullptr;
  a.sz = 0;
}


X& X::operator=(X&& a)
{
  cout << "here1\n";
  p = a.p;
  sz = a.sz;
  a.p = nullptr;
  a.sz = 0;
  return *this;
}

int& X::operator[](int x)
{
  for (auto &i : *p) {
    if (x == 0) return i;
    --x;
  }

  throw std::out_of_range("List container");
}

int& X::operator[](int x) const
{
  for (auto &i : *p) {
    if (x == 0) return i;
    --x;
  }

  throw std::out_of_range("List container");
}

X operator+(const X& a, const X& b)
{
  if (a.size()!=b.size())
    throw X::size_mismatch{};

  X res(a.size());

  for (int i = 0; i != a.size(); ++i)
    res[i] = a[i] + b[i];

  return res;
}

int main(int argc, char *argv[])
{
  X px = {0, 1, 2};

  for (int i=0; i < px.size(); i++)
    cout << px[i];
  cout << '\n';

  X py(px);
  for (int i=0; i < py.size(); i++)
    py[i]++;
  for (int i=0; i < py.size(); i++)
    cout << py[i];
  cout << '\n';

  X pz;
  pz = py;
  for (int i=0; i < pz.size(); i++)
    cout << pz[i];
  cout << '\n';

  X ph;
  // This should call move constructor
  ph = px + py + pz;
  for (int i=0; i < ph.size(); i++)
      cout << ph[i];
  cout << '\n';

  return 0;
}

$ g++ -std=c++11 test62.cc && ./a.out
012
123
123
here1
258
notaorb
  • 1,944
  • 1
  • 8
  • 18
  • Not what you asked for, but a small review: 1. You forgot the destructor. 2. There is no need to allocate the list on the heap, since it will store its *elements* on the heap regardless. If you simply do `list p;`, then you can remove the custom copy/move constructors and the assignment operators. 3. You don't need to store the size because `std::list` already stores it. 4. [`using namespace std;`](https://stackoverflow.com/questions/1452721/why-is-using-namespace-std-considered-bad-practice) is not a good thing. – HolyBlackCat May 25 '20 at 22:21
  • Not what I asked for... but thanks. I was trying to demonstrate move() which is why I chose the allocate on the heap. Right on size! – notaorb May 25 '20 at 22:23

1 Answers1

2

In this snippet:

X ph;
// This should call move constructor
ph = px + py + pz;

ph is already created, so there is no question of calling the move constructor.

If you change the snippet to:

X ph = px + py + pz;

you still won't call the move constructor, due to copy-elision.

You can see the move constructor being invoked if you explicitly std::move the result in the return statement of operator+:

X operator+(const X& a, const X& b)
{
  // ...
  return std::move(res);
}

Here's a demo.

cigien
  • 57,834
  • 11
  • 73
  • 112
  • You're correct! I'm curious now why Stroustrup C++ 4th Ed Page 76 says the move constructor should be called on return. "When z is destroyed, it too has been moved from (by the return) " – notaorb May 25 '20 at 22:21
  • I'm not sure what code is being talked about there, so I can't really answer that, sorry. – cigien May 25 '20 at 22:23
  • The move constructor will be called on return if the move wasn't elided altogether. This is an optimization compilers are allowed to make, since moves aren't supposed to have side effects anyway – Alecto Irene Perez May 25 '20 at 22:25
  • @J.AntonioPerez can you point me to this optimization of elided together? Confusing because Stroustrup's 4th Ed leads me to believe move() is called on return... Thanks – notaorb May 25 '20 at 22:27
  • 1
    See [this](https://en.cppreference.com/w/cpp/language/copy_elision). Even if move is called, the compiler can elide it. – cigien May 25 '20 at 22:35