0

I want to use C++11 move semantics. And I wrote the following class:

class ColorM
{
public:
    ColorM(float _r, float _g, float _b, float _a){
        qDebug()<<"Constructor";
        r = _r;
        g = _g;
        b = _b;
        a = _a;

        m = new float[16];
    }


    ColorM(const ColorM &other){
        qDebug()<<"Copy Constructor";
    }


    ~ColorM(){
        if (m != nullptr)
        {
           qDebug()<<"Deleting resource.";
           // Delete the resource.
           delete[] m;
        }
    }

    // Move constructor.
    ColorM(ColorM&& other)
    {
        qDebug()<<"Move Constructor";
       r = other.r;
       g = other.g;
       b = other.b;
       a = other.a;
       m = other.m;


       other.m = nullptr;
    }


    float r;
    float g;
    float b;
    float a;

    float *m;
private:
};

When I try to:

std::vector<ColorM> vec;
vec.push_back(ColorM(0.1, 0.6, 0.3, 0.7));
vec.push_back(ColorM(0.2, 0.6, 0.3, 0.7));
vec.push_back(ColorM(0.3, 0.6, 0.3, 0.7));

I got copy constructor calls. What I doing wrong?
I have used this as example. And compile it with g++.

Here is QT project I used for my tests: http://wikisend.com/download/261514/MoveConstructor.zip

tower120
  • 5,007
  • 6
  • 40
  • 88
  • 1
    Make the move constructor noexcept. – Kerrek SB Nov 23 '13 at 16:12
  • @Kerrek SB - And how noexcept helps? I have read your answer http://stackoverflow.com/questions/8001823/how-to-enforce-move-semantics-when-a-vector-grows. Than I look at this - http://msdn.microsoft.com/en-us/library/vstudio/dd293665.aspx. And in MSDN version no noexcept. – tower120 Nov 23 '13 at 16:54
  • @tower120: Think about the reallocation. Reallocation has to either succeed entirely, or not happen at all. – Kerrek SB Nov 23 '13 at 16:56
  • @Kerrek SB And what about MSDN version http://msdn.microsoft.com/en-us/library/vstudio/dd293665.aspx ? – tower120 Nov 23 '13 at 16:58
  • @tower120: Ask the author? – Kerrek SB Nov 23 '13 at 17:01
  • @tower120 The article is about Visual Studio 2010, and in 2009-2010, some important changes happened to move constructors & exceptions; see [this paper](http://www.open-std.org/JTC1/SC22/WG21/docs/papers/2009/n2855.html) and [the solution](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2010/n3050.html). – dyp Nov 23 '13 at 21:11
  • @DyP Will notice that. Though MSDN article a little bit outdated ... – tower120 Nov 25 '13 at 10:22

2 Answers2

1

You can use std::vector<T>::emplace_back function

std::vector<ColorM> vec;
vec.emplace_back(0.1, 0.6, 0.3, 0.7);  // Will call 
vec.emplace_back(0.2, 0.6, 0.3, 0.7);  //     ColorM::ColorM(float _r, float _g, ..) ctor
vec.emplace_back(0.3, 0.6, 0.3, 0.7);

Or (use ColorM move constructor)

std::vector<ColorM> vec;
vec.emplace_back(ColorM(0.1, 0.6, 0.3, 0.7));  // Should be equivalent to your code
vec.emplace_back(ColorM(0.2, 0.6, 0.3, 0.7));
vec.emplace_back(ColorM(0.3, 0.6, 0.3, 0.7));

Note: I disagree with the answer by user2485710. Since the ColorM(0.1, 0.6, 0.3, 0.7) argument to push_back is a temporary, I expect the compiler to recognize it as an r-value reference and apply the move constructor, without using std::move, since push_back has an overload for r-value references.

In other words, the emplace_back taking a ColorM object should avoid copies like push_back; in the case of emplace_back, you will necessarily construct in place.

With the second version above, you should get better diagnostic messages compared to push_back, i.e. a failure at compile time instead of this subtle unexpected use of the copy constructor.

NicholasM
  • 4,557
  • 1
  • 20
  • 47
  • *"In other words, the emplace_back should behave the same as push_back"* Almost; `emplace_back` doesn't even need to move, it just constructs the value in-place. – dyp Nov 23 '13 at 21:26
  • Thanks, DyP, I will edit to clarify. I was referring to `vec.emplace_back(ColorM(0.1, 0.6, 0.3, 0.7));`, which will construct in place using the move constructor `ColorM(ColorM&& other)` – NicholasM Nov 23 '13 at 21:59
  • `emplace_back` uses `std::forward` **and** `std::move` to construct the object , that's not against what I said, those 2 are just 2 casting, the only difference is that 1 is unconditional where the other one is indeed conditional, and `emplace_back` does all this internally. Don't forget 1 thing: what can be an rvalue or a prvalue or whatever, is an _expression_ **not** an object, which means that you probably need the semantics before judging what is what. – user2485710 Nov 24 '13 at 06:44
0

That's because of the signatures for your constructors and the argument given to your push_back() operation.

In a nutshell T() generates a new instance of an object of type T, if this instance it's not related to a label/variable, the instance remains unnamed, but his doesn't change its own type, it's always "something" of type T ( it could be even considered as T& or const T& but that's not the argument of this topic).

To reach for the T&& signature you need to cast to T&& and in C++11 this is a job for std::move which is also an unconditional kind of cast, so it's quite easy to use pretty much anywhere.

You can add std::move to your constructor inside your class or inside the push_back, your call.

user2485710
  • 9,451
  • 13
  • 58
  • 102
  • Please, look here: http://msdn.microsoft.com/en-us/library/vstudio/dd293665.aspx . Msdn says that it is enought just to v.push_back(MemoryBlock(25)); – tower120 Nov 23 '13 at 16:52
  • @tower120 they just choose to use the `std::move` cast **inside** the move constructor. – user2485710 Nov 23 '13 at 16:58
  • There is no word about exception at all! But I guess you're right. – tower120 Nov 23 '13 at 17:03
  • @tower120 the main point is that `std::move` is a casting to `T&&`, once you are familiar with that, it's only a matter of design and where you consider more appropriate to use and implement this conversion. – user2485710 Nov 23 '13 at 17:06
  • Maybe, but I thought that MSDN article will teach me that :) – tower120 Nov 23 '13 at 17:08
  • This is not correct. When you create a value via `T()`, this expression yields a prvalue. Prvalues, or, more generally rvalues, bind "stronger" to rvalue references than they do to lvalue references. `push_back` has two overloads; one taking a `const T&` (invoking a copy) and one taking a `T&&` (invoking a move). The latter is preferred for an rvalue argument expression. – dyp Nov 23 '13 at 21:23
  • @DyP what is not correct exactly ? can you show a way to obtain a `T&&` without using `std::move` at runtime or in the business logic of a C++11 application? – user2485710 Nov 24 '13 at 06:36
  • References are dropped from the type for expressions. Let's say we have two functions with signatures `void foo(ColorM const&)` (A) and `void foo(ColorM&&)` (B). Then, with `ColorM c(0,0,0,0);` we call 1) `foo(c);` 2) `foo(ColorM(0,0,0,0));` 3) `foo(std::move(c));`. The argument expression in 1) is an lvalue of type `ColorM`, so only (A) is viable. In 2), the argument expression is a prvalue of type `ColorM`, so (A) **and** (B) are viable; however, (B) is preferred. In 3), it is an xvalue (= rvalue *and* glvalue) of type `ColorM`, and overload resolution is the same as for 2). – dyp Nov 24 '13 at 11:11