Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Move Semantics C++11 (Bjarne Stroustrup book, pg75)

Tags:

c++

c++11

vector

I'm trying to make me clear about move semantics. I'm following examples of the Bjarne Stroustrup book 4th edition and I'm really lost.

He says that copy of objects can be expensive when a lot of elements (in class vector) so move semantics is the solution.

Think something like:

Vector result = vector1 + vector2 + vector3;

Order may be wrong but it will do (vector2 + vector3) generating partial result result1, and result1 + vector1, generating result;

I overloaded operator+:

Vector operator+(const Vector &a,const Vector &b)
{

  if(a.size()!=b.size()) {
      throw length_error{"Length of vectors must match for summing"};
  }

  Vector result(a.size());
  cout << "Intermediate result for sum created" << endl;
  for(long unsigned  int i=0; i<result.size();i++){
      result[i] = a[i] + b[i];
  }
  return result;
}

I also created a move constructor:

Vector::Vector(Vector&& orig) noexcept
  :elem{orig.elem},
   sz{orig.sz}
{
  cout << "Move constructor called" << endl;
  orig.elem = nullptr;          // We own the array now
  orig.sz = 0;
}

So no copy operations are performed. But there are some things I don't understand. One is related to just c++ and the other to c++11.

So first:

As you can see, when operator+ exits Vector result should be destroyed. But not after the contents are copied to another instance of vector. What I called partial result1.

This never happens, the output of the program is like this:

  • New class vector created <-- Constructor of partial result 1
  • Intermediate result for sum created <-- Operator+ applied
  • New class vector created <-- Constructor of partial result 2 -> goes to result
  • Intermediate result for sum created <-- Operator+ applied
  • Class vector destroyed <-- I undenderstand that partial result 1 destroyed.

But I don't see the second partial result destroyed until the end of the program. Nor I see any copy operations (I have done operator= and copy constructor).

So I don't understand that. If the scope of the variable Vector result is inside operator+ the contents MUST be copied to another variable. That's not done. It seems that last result of the sum is just assigned to Vector result of the sum.

Second:

No move operations are performed. I cannot get move semantics to work here. I took a look to http://gcc.gnu.org/bugzilla/show_bug.cgi?id=52745 and applied what's there but no move operations anyway.

Every result is right.

So clearly, there is something I'm doing wrong.

You can grab the whole code (with autotools config + eclipse) from here:

http://static.level2crm.com/cx11-learn-20140218.tar.bz2

Can someone explain what happens here?

Best regards,

like image 931
Gonzalo Aguilar Delgado Avatar asked Feb 18 '14 11:02

Gonzalo Aguilar Delgado


1 Answers

First and foremost, there is no move-construction here. There is NRVO occurring (as per comments on your question) but if you want to testify of move-construction occurring, you should try to copy a temporary. Try re-implementing your operator as follows:

// Note that the first parameter is taken by value.
Vector operator+(Vector a, const Vector &b)
{
  cout << "Intermediate result for sum created or moved from a" << endl;

  // NOTE: Could be implemented as operator+= and written a += b.
  if(a.size()!=b.size()) {
      throw length_error{"Length of vectors must match for summing"};
  }

  for(long unsigned  int i=0; i<a.size();i++){
      a[i] += b[i];
  }
  // END NOTE

  return a;
}

Note that your operator now requires a local "copy" as first parameter. On the first sum, since a Vector& will be provided as argument (the object has a name), the copy constructor will be called and create an object with a new buffer (what you are doing with your result local variable). Then, a temporary will be formed, so on the second call to your operator+, a Vector&& will be provided as first argument and the move-constructor will be called, effectively stealing the buffer of the temporary.

If you prevent constructor elision as per comments, you should be able to see some move constructors called.

EDIT: A more detailed explanation of why you won't have a move in your case. The short version is that C++ does not do magic :)

The aim of a move in the case of chained additions is to avoid repeatedly creatingthe buffers of your Vectors due to the creation of temporaries. Instead of losing the buffers to the destruction of the temporaries, the aim is to reuse the buffer of the temporary to store the result.

In your case, you're not even trying to copy the buffer of the Vector. You're explicitly creating a new buffer on this line:

Vector result(a.size());

You're saying "Please create a vector of the right size for the result", and doing it once for every addition. You had better say "Please steal the buffer of a" if you know that a will die soon. It is written:

Vector result(a);

But for this to work, a must refer to an object that will die soon, and this means a must be of type Vector&&. When this is done, result already contains the values of a, so all you have to do is add the values of the elements of b.

for(long unsigned  int i=0; i<result.size();i++){
  result[i] += b[i];
}

(Expressing + in terms of += is idiomatic) So to testify of move construction, you should have a definition of your operator+ whose signature is:

Vector operator+(Vector&& a, const Vector &b)  {
  if(a.size()!=b.size()) {
    throw length_error{"Length of vectors must match for summing"};
  }

  Vector result(a);
  cout << "Intermediate result for sum moved from a" << endl;
  for(long unsigned  int i=0; i<result.size();i++){
    result[i] += b[i];
  }
  return result;
}

But this won't work with the first addition, since it is not adding a temporary to another named object, but two named objects. You should have an overload of your operator+ that copes with lvalue references:

Vector operator+(const Vector& a, const Vector &b) {
  if(a.size()!=b.size()) {
    throw length_error{"Length of vectors must match for summing"};
  }

  Vector result(a); // In this case, this is a copy
  cout << "Intermediate result for sum created" << endl;
  for(long unsigned  int i=0; i<result.size();i++){
    result[i] += b[i];
  }
  return result;
}

Notice that both versions share the same text. Which brings to another idiomatic way of doing: letting the compiler decide of whether to do the move or the copy by requiring the left side of operator+ to be passed by value, requiring only one overload. (This is the version at the beginning of my answer.

like image 73
Laurent LA RIZZA Avatar answered Oct 25 '22 18:10

Laurent LA RIZZA