4
#include<iostream>
#include<stdio.h>

using namespace std;
class Test
{    
   public:
       string n;
       Test():n("test") {}  
};

int main()
{
    Test t1;  
    std::cout<<"before move"<<"\n";
    std::cout<<"t1.n=" << t1.n<<"\n";
    Test t2=std::move(t1);

    std::cout<<"after move"<<"\n";
    std::cout<<"t1.n="<<t1.n<<"\n";
    std::cout<<"t2.n="<<t2.n<<"\n"; 

    return 0;
}

Output of the above program produces below result

before move t1.n=test after move t1.n= t2.n=test

Understood that, after moving the object t1 to t2, value of t2.n results as empty string

But the same concept move concept doesn't work with integer.

#include<iostream>
#include<stdio.h>

using namespace std;

class Test
{

    public:
        int n;
        Test():n(5) {}  

};

int main()
{
    Test t1;  
     std::cout<<"before move"<<"\n";
     std::cout<<"t1.n=" << t1.n<<"\n";
     Test t2=std::move(t1);

     std::cout<<"after move"<<"\n";
     std::cout<<"t1.n="<<t1.n<<"\n";
     std::cout<<"t2.n="<<t2.n<<"\n"; 

     return 0;
}

Output of the above program produces below result

before move t1.n=5 after move t1.n=5 t2.n=5

After moving the object t1 to t2, i expected the value of t2.n as 0 but the old value still exists.

Can someone please explain the concept behind this behavior.

Balan K
  • 356
  • 2
  • 11
  • Unlike `std::string`, `int` does not have a default constructor that sets it to an empty value. Does that answer your question? – Mr Lister Jun 09 '17 at 09:57
  • @MrLister having a default constructor has no effect here. Initializing a fundamental type from an rvalue (i.e. moving) simply doesn't modify the source object. – eerorika Jun 09 '17 at 12:19

4 Answers4

5

In general, a moved-from object can have any value which is valid for its type. For example, a moved-from std::string may become empty, or it may be something completely different. It might sometimes be empty and sometimes not. There is no restriction on what it can be, and the exact value should not be relied upon.

Since the moved-from object can be in any valid state, we can see that copying an object is a valid way of moving it. In fact, for any type which does not define a move constructor, the copy constructor will be used instead when moving the object. std::move does not require the moved-from object to become empty (a concept that we can't necessarily define for all types). Of course, copying may not be the most efficient way to move an object, but it is allowed. Primitive types basically capitalise on this, so moving a primitive type is equivalent to copying it.

I'd like to reemphasise this: do not (generally) rely on the value of a moved-from object. It is not usually a specified value. Do not assume that a moved-from object is the same as a default-constructed object. Do not assume it to be any other value. Some specific types such as int or the standard smart pointer types may specify the moved-from value, but these are special cases and do not define the general rule. It is generally wise to not use a moved-from object until you copy a known value into it.

Ken Wayne VanderLinde
  • 18,915
  • 3
  • 47
  • 72
  • `any type which does not define a move constructor, the copy constructor will be used`. I would like to elaborate, that classes that don't explicitly define move constructors can still have a move constructor implicitly defined. – eerorika Jun 09 '17 at 12:47
  • Thanks Ken for your explanation. You are a superstar ! – Balan K Jun 09 '17 at 15:36
  • Your warnings regarding the use of a moved-from object are a bit too strong. The moved-from object is guaranteed to be in a valid state, i.e. using it is not undefined. What that state is, however, is unspecified, except for built-in types. – Walter Jun 09 '17 at 15:48
  • @user2079303 Yes, it's true. A move constructor will be implicitly defined as long as the class has no user-declared copy constructor, copy assignment operator, move assignment operator or destructor. – Ken Wayne VanderLinde Jun 09 '17 at 17:42
  • @Walter Isn't that exactly what I said? – Ken Wayne VanderLinde Jun 09 '17 at 18:10
4

Moving in C++ is what is called shallow copy in other languages.

copy: If an object stores data in dynamically allocated memory, then a (deep) copy implies (1) allocating an equivalent block of memory and (2) copying all elements from one block to the other. This preserves the copied-from object and creates a new, completely independent copy.

move: If the same object is moved, then only the data actually stored with the object (but not the data stored in the dynamically allocated memory) are copied over (actually, they are moved over, i.e. this is recursive), i.e. the pointer variable holding the address of the memory block and information about the size. At the same time, the moved-from object is 'emptied', i.e. put into a (valid) state which upon destruction does not affect the moved-to object. This implies that the pointer to the memory block of the moved-from object must be reset to nullptr and the variables referring to the size of memory be reset to zero, the process you called emptying.

Now, for objects that don't use dynamically allocated memory, in particular all builtin types (such as int), there is no difference between moving and copying. In particular, leaving the moved-from variable in its original state, i.e. making a copy, is just fine and actually demanded by the standard. (The standard could have left this unspecified or demand a reset to a default value, i.e. 0, but that's not how things are.)

See also here for a loooong description of move semantics.

Walter
  • 44,150
  • 20
  • 113
  • 196
  • 1
    `The compiler could reset it to 0 (another valid state)` No, the compiler couldn't. It's not only the most sensible implementation to not modify the right hand side, it is also required by the standard: `if the subobject is of scalar type, the built-in assignment operator is used.` The built-in assignment operator does not modify the right hand side. – eerorika Jun 09 '17 at 12:37
  • Thanks Walter for your comments – Balan K Jun 09 '17 at 15:40
2

If you generally need to have built in types to be set to 0 like int, float, pointers, because you may rely on them at the destruction, e.g. a user defined API pointer, you need to explicitly write a move operator to set these members to zero.

class MyClass
{
    std::string str_member_; // auto moved and emptied
    int a_value_;            // need to manually set to 0
    void* api_handle_;       // ''
public:

    // boilerplate code
    MyClass(MyClass&& rhd)
    {
        *this = std::move(rhd);
    }
    MyClass& operator=(MyClass&& rhd)
    {
        if (this == &rhd)
            return *this;

        str_member_ = std::move(rhd.str_member_);
        a_value_ = rhd.a_value_;
        api_handle_ = rhd.api_handle_;

        rhd.a_value_ = 0;
        rhd.api_handle_ = 0;

        return *this;
    }
};

I generally do not like this since its error prone when new members gets added to the class. They needed to get added to the boilerplate code. Instead you can use a small helper class that sets specific members to 0 when moved with the default move sematics.

template<class T>
class ZeroOnMove
{
    T val_;
public:
    ZeroOnMove() = default;
    ZeroOnMove(ZeroOnMove&& val) = default;
    ZeroOnMove(const T& val) : val_(val) {}

    ZeroOnMove& operator=(ZeroOnMove&& rhd)
    {
        if (this == &rhd)
            return *this;
        val_ = rhd.val_;
        rhd.val_ = 0;
        return *this;
    }
    operator T() 
    {
        return  val_;
    }
};

The class before then just gets to:

class MyClass
{
public:
    std::string str_member_;
    ZeroOnMove<int> a_value_;
    ZeroOnMove<void*> api_handle_;
};

Maybe this also helps to understand the move semantics a little bit more.

fimbox
  • 21
  • 2
0

Implicit Move constructor doesn't work fine for member non-class type But if you use explicit Move Constructor, In that case you can use exchange function for non-class type.

#include <utility>
std::exchange(old_object, default_value) //explicit move of a member of non-class type

below is the example

#include<iostream>
#include<string>
#include <utility>

struct A
{
    std::string name;
    int age;
    A(){
        std::cout << "Default ctor. ";
    }

    //explicit
    A(std::string const& s, int x):name(s), age(x){
        std::cout << "Ctor. ";
    }

    A(A const& a):name(a.name),age(a.age){
        std::cout << "Copy ctor. ";
    }

    A(A && a) noexcept :name(std::move(a.name)),age(std::exchange(a.age,0)){
        std::cout << "Move ctor. ";
    }

    A& operator=(A const& a){
        std::cout << "Copy assign. ";
        name = a.name;
        age = a.age;
        return *this;
    }

    A& operator=(A && a) noexcept {
        std::cout << "Move assign. ";
        name = std::move(a.name);
        age = std::move(a.age);
        return *this;
    }

    void printInfo()
    {
        std::cout<<name<<"   "<<age<<std::endl;
    }

    ~A() noexcept {
        std::cout << "Dtor. ";
    }
};
int main()
{
    A a("StackOverflow ", 12);
    a.printInfo();
    A b = std::move(a);
    b.printInfo();
    a.printInfo();
    return 0;
}

for more information https://en.cppreference.com/w/cpp/language/move_constructor