6

I am learning the behavior of std::vector: how it relocates (copies/moves) objects when reserving more capacity:

#include<vector>
#include<iostream>
using namespace std;

struct A{
   A(const A& a){ i=a.i; cerr<<"copied "<<i<<endl; }  //(1)
   A(     A&& a){ i=a.i; cerr<<"moved "<<i<<endl; }
   A(int i):i(i){cerr<<"created "<<i<<endl;};
   A(){};
private:
   int i=0;
};
int main()
{
   vector<A> v1;
   size_t prevcap=v1.capacity();
   for(int i=0; i<10; ++i){
      v1.emplace_back(i);
      if(prevcap!=v1.capacity()){
         cerr<<"capacity increased to "<<v1.capacity()<<endl;
         prevcap=v1.capacity();
      }
      cerr<<"------"<<endl;
   }
}

With my g++-10 the output is:

created 0
capacity increased to 1
------
created 1
copied 0
capacity increased to 2
------
created 2
copied 0
copied 1
capacity increased to 4
------
created 3
------
created 4
copied 0
copied 1
copied 2
copied 3
capacity increased to 8
------
created 5
------
created 6
------
created 7
------
created 8
copied 0
copied 1
copied 2
copied 3
copied 4
copied 5
copied 6
copied 7
capacity increased to 16
------
created 9
------

If remove copy-construtor (1) the output would be:

created 0
capacity increased to 1
------
created 1
moved 0
capacity increased to 2
------
created 2
moved 0
moved 1
capacity increased to 4
------
created 3
------
created 4
moved 0
moved 1
moved 2
moved 3
capacity increased to 8
------
created 5
------
created 6
------
created 7
------
created 8
moved 0
moved 1
moved 2
moved 3
moved 4
moved 5
moved 6
moved 7
capacity increased to 16
------
created 9
------

Why if both copy and move ctors exist is the copy-ctor preferred?


Update. Thx to @Jarod42. Add noexcept

struct A{
   A(const A& a)noexcept{ i=a.i; cerr<<"copied "<<i<<endl; }
   A(     A&& a)noexcept{ i=a.i; cerr<<"moved "<<i<<endl; }
   A(int i)noexcept:i(i){cerr<<"created "<<i<<endl;};
   A()noexcept{};
private:
   int i=0;
};

output:

created 0
capacity increased to 1
------
created 1
moved 0
capacity increased to 2
------
created 2
moved 0
moved 1
capacity increased to 4
------
created 3
------
created 4
moved 0
moved 1
moved 2
moved 3
capacity increased to 8
------
created 5
------
created 6
------
created 7
------
created 8
moved 0
moved 1
moved 2
moved 3
moved 4
moved 5
moved 6
moved 7
capacity increased to 16
------
created 9
------

Now moved. But what is the logic behind this?

Boann
  • 48,794
  • 16
  • 117
  • 146
kyb
  • 7,233
  • 5
  • 52
  • 105
  • 14
    Add `noexcept` to your move constructor. – Jarod42 Nov 26 '20 at 13:02
  • It works. Thank you for response. Now vector prefers move-ctor. Even if add `noexcept` to both ctors. Still the question why? – kyb Nov 26 '20 at 13:05
  • 1
    It'll only move elements if move constructor won't throw, otherwise it can't keep the strong exception guarantee. – jrok Nov 26 '20 at 13:06
  • @kyb if the move constructor is noexcept, std::vector will always prefer the move constructor. – Guillaume Racicot Nov 26 '20 at 13:08
  • https://en.cppreference.com/w/cpp/language/move_constructor <- see Notes –  Nov 26 '20 at 13:09
  • 1
    @kyb It's caused by this sentence: http://eel.is/c++draft/vector#capacity-4.sentence-4. You cannot have _strong exception guarantee_ (no effect) with throwing move constructor, that's why copy constructor has higher priority. – Daniel Langr Nov 26 '20 at 13:09
  • 1
    You might read [does-c11-standard-require-implementers-to-prioritize-noexcept-move-constructor](https://stackoverflow.com/questions/46409188/does-c11-standard-require-implementers-to-prioritize-noexcept-move-constructor) – Jarod42 Nov 26 '20 at 13:09
  • @Jarod42 Why not answer? – Red.Wave Nov 26 '20 at 13:15
  • 1
    @jrok but the copy ctor also doesn't guarantee nothrow. why then prefer it against the move ctor? – arsdever Nov 26 '20 at 13:15
  • 4
    @arsdever Because with copy constructor, the original vector content is not modified during reallocation and you can do the rollback in case of exception. With move constructors, you can do the rollback only again with move operations, but if they can throw, you are doomed ;) – Daniel Langr Nov 26 '20 at 13:17
  • @DanielLangr makes sense. Thx – arsdever Nov 26 '20 at 13:18
  • @Red.Wave: I was looking for a dup. I know how to resolve it, but not really the why. – Jarod42 Nov 26 '20 at 13:38
  • @Jarod42 This might be one: [are std::vector required to use move instead of copy?](https://stackoverflow.com/q/47017595/580083) Another ones: [Why does reallocating a vector copy instead of moving the elements?](https://stackoverflow.com/q/10127603/580083), [When std::vector reallocate its memory array, is copy constructor or move constructor used?](https://stackoverflow.com/q/45316155/580083), [How to enforce move semantics when a vector grows?](https://stackoverflow.com/q/8001823/580083) – Daniel Langr Nov 26 '20 at 13:50

0 Answers0