When Does Move Constructor get called?

22,588

Solution 1

A move constructor is called:

  • when an object initializer is std::move(something)
  • when an object initializer is std::forward<T>(something) and T is not an lvalue reference type (useful in template programming for "perfect forwarding")
  • when an object initializer is a temporary and the compiler doesn't eliminate the copy/move entirely
  • when returning a function-local class object by value and the compiler doesn't eliminate the copy/move entirely
  • when throwing a function-local class object and the compiler doesn't eliminate the copy/move entirely

This is not a complete list. Note that an "object initializer" can be a function argument, if the parameter has a class type (not reference).

a RetByValue() {
    a obj;
    return obj; // Might call move ctor, or no ctor.
}

void TakeByValue(a);

int main() {
    a a1;
    a a2 = a1; // copy ctor
    a a3 = std::move(a1); // move ctor

    TakeByValue(std::move(a2)); // Might call move ctor, or no ctor.

    a a4 = RetByValue(); // Might call move ctor, or no ctor.

    a1 = RetByValue(); // Calls move assignment, a::operator=(a&&)
}

Solution 2

First of all, your copy constructor is broken. Both the copied from and copied to objects will point to the same Array and will both try to delete[] it when they go out of scope, resulting in undefined behavior. To fix it, make a copy of the array.

a::a(const a& Old): Array(new int[5])
{
  for( size_t i = 0; i < 5; ++i ) {
    Array[i] = Old.Array[i];
  }
}

Now, move assignment is not being performed as you want it to be, because both assignment statements are assigning from lvalues, instead of using rvalues. For moves to be performed, you must be moving from an rvalue, or it must be a context where an lvalue can be considered to be an rvalue (such as the return statement of a function).

To get the desired effect use std::move to create an rvalue reference.

A=C;              // A will now contain a copy of C
B=std::move(C);   // Calls the move assignment operator
Share:
22,588
Lauer
Author by

Lauer

Updated on December 20, 2020

Comments

  • Lauer
    Lauer over 3 years

    I'm confused about when a move constructor gets called vs a copy constructor. I've read the following sources:

    Move constructor is not getting called in C++0x

    Move semantics and rvalue references in C++11

    msdn

    All of these sources are either overcomplicated(I just want a simple example) or only show how to write a move constructor, but not how to call it. Ive written a simple problem to be more specific:

    const class noConstruct{}NoConstruct;
    class a
    {
    private:
        int *Array;
    public:
        a();
        a(noConstruct);
        a(const a&);
        a& operator=(const a&);
        a(a&&);
        a& operator=(a&&);
        ~a();
    };
    
    a::a()
    {
        Array=new int[5]{1,2,3,4,5};
    }
    a::a(noConstruct Parameter)
    {
        Array=nullptr;
    }
    a::a(const a& Old): Array(Old.Array)
    {
    
    }
    a& a::operator=(const a&Old)
    {
        delete[] Array;
        Array=new int[5];
        for (int i=0;i!=5;i++)
        {
            Array[i]=Old.Array[i];
        }
        return *this;
    }
    a::a(a&&Old)
    {
        Array=Old.Array;
        Old.Array=nullptr;
    }
    a& a::operator=(a&&Old)
    {
        Array=Old.Array;
        Old.Array=nullptr;
        return *this;
    }
    a::~a()
    {
        delete[] Array;
    }
    
    int main()
    {
        a A(NoConstruct),B(NoConstruct),C;
        A=C;
        B=C;
    }
    

    currently A,B,and C all have different pointer values. I would like A to have a new pointer, B to have C's old pointer, and C to have a null pointer.

    somewhat off topic, but If one could suggest a documentation where i could learn about these new features in detail i would be grateful and would probably not need to ask many more questions.

  • RainingChain
    RainingChain about 7 years
    B=std::move(C); // Calls the copy assignment operator Shouldn't it be the move assignment operator? Afterwards, C may no longer be what it used to.
  • artm
    artm about 6 years
    a a4 = RetByValue(); // Might call move ctor, or no ctor. //<-- might or not - what does it depend on? is it something to do with compiler optimisation? thanks
  • aschepler
    aschepler about 6 years
    @artm Yes, it's up to the compiler. And actually C++17 changed the rules somewhat so that some additional cases guarantee fewer constructor and destructor calls.
  • bitfox
    bitfox about 4 years
    "Yes, it's up to the compiler" ... that's what makes some people crazy! :-) In my case, I got an error when I tried to delete the default move constructor (I mean: "class_name ( class_name && ) = delete;") . The error was: "error: use of deleted function 'class_name::class_name(class_name&&)" . The code error line was "class_name test = class_name("test");". Then I tried to define "my custom move constructor" which looks like to be called only when I use "std::move". Is it also up to the compiler? Thanks in advance! P.s.: I apologise for the long comment...
  • aschepler
    aschepler about 4 years
    @bitfox The compiler doesn't get to choose whether the code is correct, only in some cases what actually happens if it is valid. The statement class_name test = class_name("test"); when class_name has an explicitly deleted move constructor is ill-formed in C++11 and C++14, but well-formed in C++17 because there is no move or copy constructor involved. I don't think you can detect a difference between a std::move(e) expression and other rvalue expressions.
  • bitfox
    bitfox about 4 years
    @aschepler Thank you for your reply. Your suggestion about C++ version and the "ill-formed", spur me to make order about some points. This howardhinnant.github.io/classdecl.html and stackoverflow.com/questions/37092864 make me clear a lot of stuffs.