26

I've been testing some C++11 features from some some. I came across r-value references and move constructors.

I implemented my first move constructor, here it is:

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

class TestClass{

public:
    TestClass(int s):
        size(s), arr(new int[s]){
    }
    ~TestClass(){
        if (arr)
            delete arr;
    }
    // copy constructor
    TestClass(const TestClass& other):
            size(other.size), arr(new int[other.size]){
        std::copy(other.arr, other.arr + other.size, arr);
    }

    // move constructor
    TestClass(TestClass&& other){
        arr=other.arr;
        size=other.size;

        other.arr=nullptr;
        other.size=0;
    }

private:
    int size;
    int * arr;
};

int main(){
    vector<TestClass> vec;

    clock_t start=clock();
    for(int i=0;i<500000;i++){
        vec.push_back(TestClass(1000));
    }
    clock_t stop=clock();
    cout<<stop-start<<endl;

    return 0;
}

The code works fine. Anyway putting a std::cout inside the copy constructor i noticed that it gets called! And a lot of times.. (move constructor 500000 times, copy constructor 524287 times).

What surprised me more is that if i comment out the copy constructor from the code, the whole program gets a lot faster, and this time the move constructor is called 1024287 times.

Any clue?

R. Martinho Fernandes
  • 228,013
  • 71
  • 433
  • 510
Luke Givens
  • 829
  • 1
  • 11
  • 15
  • 1
    Which compiler are you using? – doctorlove Aug 06 '13 at 16:23
  • http://coliru.stacked-crooked.com/view?id=0b61fede4fd9aef84b124760919e8ca8-4c5ca02fa47b506419b4501a6b65fb4f – R. Martinho Fernandes Aug 06 '13 at 16:23
  • @pinoscotto Just a side note: **you have undefined behavior**, you should use `delete[]`, and you don't need the `if (arr) ...` in the destructor. So the destructor should be simply `~TestClass(){ delete[] arr; }`. – Ali Aug 06 '13 at 20:02
  • 3
    @pinoscotto It is always safe to call `delete[]` without the if: (1) if the `new` throws in the initialization, the destructor won't be called (2) otherwise you either point to a valid address (3) or have a `nullptr` and it is always safe to delete a `nullptr` (guaranteed by the standard). – Ali Aug 06 '13 at 20:05

3 Answers3

38

Put noexcept on your move constructor:

TestClass(TestClass&& other) noexcept {

Elaboration: I was going to give this one Pierre, but unfortunately the cppreference source is only approximately correct.

In C++03

vector<T>::push_back(T)

has the "strong exception guarantee". That means that if the push_back throws an exception, the vector is left in the same state it had prior to the call to push_back.

This guarantee is problematic if the move constructor throws an exception.

When the vector reallocates, it would like to move the elements from the old buffer to the new. However if any one of those moves throws an exception (besides the first), then it is left in a state where the old buffer has been modified, and the new buffer doesn't yet contain everything it is supposed to. The vector can't restore the old buffer to its original state because it would have to move elements back to do so, those moves might also fail.

So a rule was laid down for C++11:

  1. If T has a noexcept move constructor, that can be used to move the elements from the old buffer to the new.

  2. Otherwise if T has a copy constructor, that will be used instead.

  3. Otherwise (if there is no accessible copy constructor), then the move constructor will be used after all, however in this case, the strong exception safety guarantee is no longer given.

Clarification: "copy constructor" in rule 2 means a constructor taking a const T&, not one of those weenie so-called T& copy constructors. :-)

Howard Hinnant
  • 206,506
  • 52
  • 449
  • 577
  • 1
    can you elaborate more on your answer, and what does noexcept do? – Alon Aug 06 '13 at 16:28
  • Sod all on Visual studio :-( apart from saying "error C3646: 'noexcept' : unknown override specifier" – doctorlove Aug 06 '13 at 16:31
  • @Alon http://accu.org/index.php/conferences/accu_conference_2013/accu2013_sessions#move_noexcept_and_push_back_and_how_it_relates_to_each_other – doctorlove Aug 06 '13 at 16:33
  • @Alon there is a relevant, albeit pre-C++11 ratification, discussion [here](http://cpp-next.com/archive/2009/10/exceptionally-moving/). Basically, it is about maintaining certain exception guarantees in standard library containers. – juanchopanza Aug 06 '13 at 16:36
  • @doctorlove: In Visual Studio, `throws()` _might_ work, though it means something slightly different which might cause it's own problems. – Mooing Duck Aug 06 '13 at 16:46
  • @MooingDuck thanks - I'm not sure that VS uses this to optimize though, I 've got lost in macros trying to follow what it does – doctorlove Aug 06 '13 at 16:53
  • So is it intended by the standard? What i find odd with C++11 is that when i discover a useful (on the paper) new sintax, it's always crippled by some corner case that impose accurate handling (with added codex complexity or a never heard before exotic sintax). async is another case – Luke Givens Aug 07 '13 at 07:31
16

Use noexcept on your move constructor :

TestClass(TestClass&& other) noexcept { ... }

noexcept without a constant expression like this is equivalent to noexcept(true).

The compiler can use this information to enable certain optimizations on non-throwing functions as well as enable the noexcept operator, which can check at compile time if a particular expression is declared to throw any exceptions.

For example, containers such as std::vector will move their elements if the elements' move constructor is noexcept, and copy otherwise.

Source : http://en.cppreference.com/w/cpp/language/noexcept_spec

NB : This is a C++11 feature. Certain compiler may not have implemented it yet... (ex: Visual Studio 2012)

Community
  • 1
  • 1
Pierre Fourgeaud
  • 14,290
  • 1
  • 38
  • 62
-1

Copy constructor is called when all reserved memory inside std::vector is used. It is necessary to call std::vector::reserve() method before adding the elements.

vector<TestClass> vec;
vec.reserve(500000);
TheEsnSiavashi
  • 1,245
  • 1
  • 14
  • 29