-2

I am trying to understand the behavior of object copying and object move when containers are involved. Here is a sample code i wrote.

#include<memory>
#include<iostream>
#include<vector>
using namespace std;
class X{
  int val;
public:
  X(int i):val(i){cout<<"constructor X"<< val <<endl;}
  X(){cout<<"constructor X"<< val <<endl;}
  X(const X &a) {cout<<"copy constructor X"<< val <<endl;}
  X(X &&a) {cout<<"move constructor X"<< val <<endl;}
  ~X(){cout<<"Distructor X"<< val << endl;}
  X& operator = (X a){ cout<<"Assignment operator"<<endl; return a;}
  void do1(){cout<<"Do"<<endl;}
};

vector<X> container_copy(){
  vector<X> a1(10);
  return a1;
}

main(){
  vector<X> b;

  vector<X> a = container_copy(); //#1 .
  b = a; // #2

  cout<<"Done"<<endl;
}

here is the sample output i got using

constructor X0
constructor X0
constructor X0
copy constructor X0
copy constructor X0
copy constructor X0
Done
Distructor X0
Distructor X0
Distructor X0
Distructor X0
Distructor X0
Distructor X0

Here is my queries.

  1. Why is the move constructor not called for statement marked #1

  2. Why is the copy constructor called instead of assignment operator for #2

I am using following command for compiling under gcc

g++ <file_name>.cpp --std=c++11

Deduplicator
  • 44,692
  • 7
  • 66
  • 118
David
  • 4,634
  • 7
  • 35
  • 42
  • You should always test things like this with *optimized* builds. With unoptimized debug builds the compiler may do sub-optimal (but allowed) things in order to provide you with something that's easier to debug. – Jesper Juhl Aug 10 '18 at 19:47
  • 2
    Your assignment operator invokes undefined behavior. You failed to return a value. – PaulMcKenzie Aug 10 '18 at 19:47
  • 3
    Behaviour of the code is moot, as this will not compile. Post compilable code. –  Aug 10 '18 at 19:47
  • 2
    In addition, if you got rid of all of the bugs, compiling with optimizations on would more than likely give you different results. – PaulMcKenzie Aug 10 '18 at 19:49
  • Try adding some items to the vector `b`. You'll see more action. – Ripi2 Aug 10 '18 at 19:50
  • @Neil . I have made the code compilable – David Aug 10 '18 at 19:52
  • @PaulMcKenzie when i compile the code with g++ test.cpp --std=c++11 -O3 i still see the same behaviour – David Aug 10 '18 at 19:53
  • 1
    You will never see any of your constructors called when the vector is returned from function, because it is a vector which is moved, not your objects. Moving a vector effectively means changing a value of a pointer, and original objects remain intact. – SergeyA Aug 10 '18 at 19:57
  • @SergeyA So i understood #1 now. but why is #2 invoking copy ctors rather than assignment operator. – David Aug 10 '18 at 20:00
  • 2
    There are several problems in this code. `const X&&` is wrong in a move ctor, use plain `X&&`. `operator =` must have a `return` statement. `main` must be declared with a type. – n. m. could be an AI Aug 10 '18 at 20:01
  • Unrelated good reading on `vector`s, exception safety, and and move operations: [Noexcept and copy, move constructors](https://stackoverflow.com/questions/28627348/noexcept-and-copy-move-constructors). May prevent a few future surprises. – user4581301 Aug 10 '18 at 21:40

3 Answers3

1

Why is the move constructor not called for statement marked #1

The vector itself isn't moved because of the return value optimisation. The function constructs its return value directly in the caller space, so there's no moving or copying. The vector elements are not moved regardless. Elements are never moved when the vector itself is copy- or move-conctructed.

Why is the copy constructor called instead of assignment operator for #2

Container aasignment works like this: throw away old elements, then copy new elements in. At the second stage the container has no elements to assign to, thus no assignment. Why throw old elements away? For consistency: the number of existing elements may be larger or smaller than the number of new elements, and we don't want complicated implementation and inconsistent results for this operation.

n. m. could be an AI
  • 112,515
  • 14
  • 128
  • 243
1

Taking your two questions in order:

In the first case, your element move-constructor will never be fired in this context, no matter what. Given your output, the function call is undergoing RVO. I.e. a is what is actually constructed as if it were the vector in the function. Even without RVO, there still wouldn't be a need for element-moving. Rather, you would see something similar to your second case which I describe next

In the second case, you asked why there were no assignment operators invoked. Copy construction is invoked because there is nothing to assign to (much less move to), so there won't be element moving going on here either. b is initially empty, remember? The result of b = a will simply reserve space for the elements in a, then then copy-construct each element out of a to finish off the b vector. No moves are required (nor wanted), of either your elements nor any vectors. You asked for a copy, and that's what you're getting.

Worth noting, had you asked for a move-assignment of the vector rather than a copy-assignment:

b = std::move(a);

you still would not get element-wise move-construction. The vector itself would be moved. If b were previously populated, those contents would be destroyed and the result would be b taking ownership of a's elements.

In short, there is zero use for a move-constructor for the given code. It will never be called, because it should never be called.

WhozCraig
  • 65,258
  • 11
  • 75
  • 141
0

Due to return value optimisation there is probably no copy/move involved in calling container_copy.

If you compile with optimisations turned on the function is almost certainly small enough to be inlined so your main function would become and even return value optimisation is unnecessary:

main(){
  vector<X> b;

  vector<X> a(10); //#1 .
  b = a; // #2

  cout<<"Done"<<endl;
}

#2 is calling the copy constructors as it is the vector itself which is being assigned which internally will be doing something like this (roughly, this code isn't optimal but just to give you the idea):

std::vector<X> operator = ( const std::vector<X>& other )
{
   clear();
   for (const auto& x : other)
   {
      push_back(X(x)); // actually its probably doing a placement new rather than push_back which would result in an additional copy
   }
}
Alan Birtles
  • 32,622
  • 4
  • 31
  • 60
  • Shouldn't the compiler be calling the move constructor during the return value optimization? – David Aug 10 '18 at 19:57
  • 1
    Inlining is not the root cause of the fact that returning vector from function doesn't call move constructor of vector elements. – SergeyA Aug 10 '18 at 19:58
  • I did see the same behavior when i compile with -g and the function is not inlined – David Aug 10 '18 at 19:58
  • @SergeyA I didn't mean that inlining was the cause, just illustrating the extreme case where the function call disappears completely due to optimisation. – Alan Birtles Aug 10 '18 at 20:01
  • @David the presence or absence of the `-g` switch shouldn't have any affect, it only enables debugging symbols. Optimisation is controlled with `-O0`, `-O3' etc. – Alan Birtles Aug 10 '18 at 20:02
  • @David no, RVO avoids the need for the move constructor – Alan Birtles Aug 10 '18 at 20:04
  • @David "Shouldn't the compiler be calling the move constructor during the return value optimization?" - No. The whole point is eliding the copy/move and just constructing directly in the return value. The source object shouldn't care, no functions need be called. – Jesper Juhl Aug 10 '18 at 20:26