user210528
user210528

Reputation: 35

c++ what really happens when you push_back or push_front in a deque

I'm reading from here: http://www.cplusplus.com/reference/deque/deque/push_back/

for

void push_back (const value_type& val);

that val is a

Value to be copied (or moved) to the new element.

e.g. if val is an instance of a class, in which case does it get copied and in which it gets moved? What is the difference between the two cases?

Can you also please explain what different happens in each one of the following add_val1, add_val2 and add_val3 ?

#include <deque>

class Value
{
    int member1;
    int member2;
};


class ValuesContainer
{
private:
    std::deque<Value> m_vals;

public:

    void add_val1(const Value &val)
    {
        m_vals.push_back( val );
    }

    void add_val2(const Value val)
    {
        m_vals.push_back( val );
    }

    void add_val3(const Value &val)
    {
        m_vals.push_back( Value(val) );
    }

};

int main()
{
    return 0;
}

Thanks!

Upvotes: 1

Views: 3105

Answers (3)

FERNman
FERNman

Reputation: 313

if val is an instance of a class, in which case does it get copied and in which it gets moved? What is the difference between the two cases?

If a value is an instance of a class, let's say for example

class MyValue {};

MyValue originalValue;
MyValue copyAssignedValue = originalValue;

It always gets copied. This happens because the copy assignment operator gets called. In a similar matter,

MyValue copyConstructedValue(originalValue);

calls the copy constructor (which, as the name indicates, copies the content).

To understand, why and when copy or move constructors are used, it is necessary to understand the difference between the two operations:

Copying an object is like a normal Copy and Paste: You now have two copies of the same object that you can work on independently. Moving an object is like Cut and Paste: You only have one working object in the end, and the old one gets deleted. It is, however, much faster then copying because you don't need to allocate new memory.

To move an object, you can do a few things. For example, you can create an object that the compiler knows you'll only need in the move constructor.

MyValue moveConstructedValue(MyValue());

Just to be clear, this is what a move constructor looks like:

MyValue::MyValue(MyValue&& other);

Note the double &&, which indicates a rvalue reference (single & are now called lvalue references).

Or you can indicate that you want to use the move constructor explicitly (be carefull, the originValue object will be reseted to default values):

MyValue valueToBeMovedTo(std::move(originalValue));

To answer your second question, which of your functions uses a move operation: Actually, none of them "move" the val into m_vals, because the compiler doesn't know if you want to use it afterwards. You could use moving though by using the new rvalue reference:

void add_valMoving(const Value &&val)
{
   m_vals.push_back( val );
}

To 100% correct, add_val3 uses a move constructor, but not directly on the val object: It first calls the copy constructor of Value and copies val into a new object, and then uses the move constructor to move the new object into the deque.


Edit:

To be more clear on the "default values" when moving objects, a move constructor could be:

class MyValue 
{
public:
    MyValue(MyValue&& other)
    {
       this->member = other.member;
       other.member = 0;
    }

private:
    int member;
}

You never know, however, to what values the original object will be set after a move.

Upvotes: 1

Caleth
Caleth

Reputation: 62576

That reference is confusing you, because it is by default showing only the copying push_back, and still talking about moving.

Since C++11, there are two push_backs

  • void push_back( const T& value ); which always copies
  • void push_back( T&& value ); which always moves

If the value you are pushing back has a name, or is explicitly a T&, then it will use the first, otherwise the second

void add_val1(const Value &val)
{
    m_vals.push_back( val ); // copy
}

void add_val2(const Value val)
{
    m_vals.push_back( val ); // copy
}

void add_val3(const Value &val)
{
    m_vals.push_back( Value(val) ); // move the temporary that is a copy of val
}

extern Value make_value_somehow() 

void add_val4()
{
    m_vals.push_back( make_value_somehow() ); // move
}

extern Value& find_value_somehow() 

void add_val5()
{
    m_vals.push_back( find_value_somehow() ); // copy
}

void add_val6(Value val)
{
    m_vals.push_back( val ); // copy
}

void add_val7(Value val)
{
    m_vals.push_back( std::move(val) ); // move
}

void add_val8(const Value val)
{
    // m_vals.push_back( std::move(val) ); // ill-formed, can't modify val
}

void add_val9(Value & val)
{
    m_vals.push_back( std::move(val) ); // move, which will confuse people
}

Upvotes: 1

Zereges
Zereges

Reputation: 5209

Value to be copied (or moved) to the new element.

This depends on overload used in push_back (see this, in my opinion, better reference page).

add_val1 takes reference and push_back copies that.
add_val2 takes value (so the function will copy it) and than it's copied again by push_back.
add_val3 takes reference, you invoke a copy, but since Value(val) is r-value, the temporary is then moved by push_back.

Upvotes: 0

Related Questions