36

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.

Community
  • 1
  • 1
Lauer
  • 517
  • 1
  • 6
  • 11
  • 1
    On a partially related matter, you might want to check the copy and swap idiom http://stackoverflow.com/questions/3279543/what-is-the-copy-and-swap-idiom for your assignement operator. – undu Oct 29 '12 at 16:29
  • [related FAQ](http://stackoverflow.com/questions/3106110/) – fredoverflow Oct 29 '12 at 17:52

4 Answers4

48

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&&)
}
aschepler
  • 70,891
  • 9
  • 107
  • 161
  • 1
    `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 – artm May 25 '18 at 12:52
  • 3
    @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. – aschepler May 25 '18 at 18:26
  • "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... – bitfox Jun 01 '20 at 18:54
  • 1
    @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. – aschepler Jun 01 '20 at 21:18
  • @aschepler Thank you for your reply. Your suggestion about C++ version and the "ill-formed", spur me to make order about some points. This http://howardhinnant.github.io/classdecl.html and https://stackoverflow.com/questions/37092864 make me clear a lot of stuffs. – bitfox Jun 04 '20 at 16:38
5

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
Praetorian
  • 106,671
  • 19
  • 240
  • 328
  • `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. – RainingChain Apr 27 '17 at 14:13
1

Answers above do not give a 'natural' example when a move constructor is called. I found this way to call move constructor without std::move (and without suppressing copy elision by -fno-elide-constructors):

a foo(a a0) {
    return a0; // move ctor is called 
}
    
a a1 = foo(a());
  • 1
    As it’s currently written, your answer is unclear. Please [edit] to add additional details that will help others understand how this addresses the question asked. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Sep 22 '22 at 04:26
0

Remember that copy elision could occur. If you disable it by passing the -fno-elide-constructors flag to the compiler your constructor might get executed.

You can read about it here: https://www.geeksforgeeks.org/copy-elision-in-c/

scrrr
  • 5,135
  • 7
  • 42
  • 52