0

I am trying to learn move semantics in modern C++. It is my understanding that this construct is useful when designing a class which manages a resource. I tried to experiment with this by building a very simple wrapper around C-style arrays, as follows.

// A wrapper around int[] to practice with the rule of three/five
#include <algorithm>
#include <cstddef>
#include <iostream>


class Vector {
public:
    Vector(const std::size_t n) : size_(n), data_(new int[n]) {
    std::cout << "called constructor" << std::endl; }
    ~Vector() { delete[] data_;
    std::cout << "called destructor" << std::endl; }
    Vector(const Vector&);
    Vector& operator=(Vector);
    friend void swap(Vector&, Vector&);
    std::size_t size() { return size_; }
    int& operator[](std::size_t i) { if (i >= size_) throw "out of bounds"; return data_[i]; }
    int operator[](std::size_t i) const { if (i >= size_) throw "out of bounds"; return data_[i]; }
    // move constructor
    Vector(Vector&& v);
    // move assignment not needed (see current copy assignment)
    //Vector& operator=(Vector&&);
private:
    std::size_t size_;
    int* data_;
};


void swap(Vector& a, Vector& b) {
    std::swap(a.size_, b.size_);
    std::swap(a.data_, b.data_);
}


Vector::Vector(const Vector& v) :
  size_(v.size_),
  data_(new int[v.size_])
{
    std::copy(v.data_, v.data_ + v.size_, data_);
    std::cout << "called copy constructor" << std::endl;
}


Vector::Vector(Vector&& v) :
  Vector(v.size_)
{
    swap(*this, v);
    std::cout << "called move constructor" << std::endl;
}


Vector& Vector::operator=(Vector v) {
    std::cout << "called copy assignement" << std::endl;
    swap(*this, v);
    return *this;
}


int main() {
    Vector a(2);
    Vector b(6);
    for (std::size_t i = 0; i < b.size(); ++i) {
        b[i] = static_cast<int>(i);
    }
    a = b;
    Vector c(b);
    Vector d(Vector(3));
}

I would expect that the last line of code, namely Vector d(Vector(3)) would call the move constructor, as I initialise d with an rvalue (or at least I think this is what I am trying to do here).

However, this is the output when executing the program:

called constructor
called constructor
called copy constructor
called copy assignement
called destructor
called copy constructor
called constructor
called destructor
called destructor
called destructor
called destructor

It seems that the code is never calling the move constructor. Why is this happening? How can I change my code so that it takes advantage of the move constructor?

I also tried changing the implementation of the move constructor as shown in this answer, namely:

Vector::Vector(Vector&& v) :
  size_(std::move(v.size_)),
  data_(std::move(v.data_))
{
    v.size_ = 0;
    v.data_ = nullptr;
    std::cout << "called move constructor" << std::endl;
}

But still the move constructor is not used.

J. D.
  • 279
  • 1
  • 9
  • 1
    `Vector d(std::move(a));` would use the move constructor. I believe the compiler is optimizing `Vector d(Vector(3))` to be the same as `Vector d(3)` to avoid constructing a temporary and then moving it. – James Adkison Dec 11 '19 at 15:00
  • @JamesAdkison compiling with -O0 does not change the output (I'm using the GNU compiler). Is there a way to disable the optimisation? – J. D. Dec 11 '19 at 15:26
  • @walnut thanks. Apart from this optimisation I was unaware of, are both my attempted move constructors fine? Is one of the two preferable over the other? – J. D. Dec 11 '19 at 15:28
  • 1
    @J.D. The second one is a bit convoluted and the first one does too much work. It allocates in the delegated constructor (`Vector(v.size_)`), while it probably just should call a default-constructor (that you are currently lacking) to set the members to `nullptr`/`0`. Then you could also make it, the default-constructor and the `swap` function `noexcept` (assuming the `cout`'s don't stay there). – walnut Dec 11 '19 at 15:41
  • 1
    I'm not sure how to disable it. Why would you want to disable it? A quick search provided the following info which may be applicable: "GCC provides the ‑fno‑elide‑constructors option to disable copy-elision" – James Adkison Dec 11 '19 at 16:28
  • @JamesAdkison this flag works, thanks. I think for people who are learning C++ it could be useful to have the compiler generate an executable that is as close as possible to what we write in the source. I also realise that C++ is very much concerned with performance, so it is better to get accustomed to the compiler doing aggressive optimisation even with the -O0 flag. I also find it strange that compiling the code with `Vector d(std::move(Vector(3))); `with the flag -Wpessimizing-move does not produce any warning that we are missing out an optimisation opportunity. – J. D. Dec 11 '19 at 16:34

0 Answers0