Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is copy/move constructor choosing rule in C++? When does move-to-copy fallback happen?

The first example:

#include <iostream>
#include <memory>
using namespace std;

struct A {
    unique_ptr<int> ref;
    A(const A&) = delete;
    A(A&&) = default;
    A(const int i) : ref(new int(i)) { }
    ~A() = default;
};

int main()
{
    A a[2] = { 0, 1 };
   return 0;
}

It works perfectly. So here the MOVE constructor is used.

Let's remove the move constructor and add a copy one:

#include <iostream>
#include <memory>
using namespace std;

struct A {
    unique_ptr<int> ref;
    A(const A&a) 
        : ref( a.ref.get() ? new int(*a.ref) : nullptr )
    {  }
    A(A&&) = delete;
    A(const int i) : ref(new int(i)) { }
    ~A() = default;
};

int main()
{
    A a[2] = { 0, 1 };
   return 0;
}

Now the compilation falls with the error "use of deleted function ‘A::A(A&&)’"
So the MOVE constructor is REQUIRED and there is no fall back to a COPY constructor.

Now let's remove both copy- and move-constructors:

#include <iostream>
#include <memory>
using namespace std;

struct A {
    unique_ptr<int> ref;
    A(const int i) : ref(new int(i)) { }
    ~A() = default;
};

int main()
{
    A a[2] = { 0, 1 };
   return 0;
}

And it falls with "use of deleted function ‘A::A(const A&)’" compilation error. Now it REQUIRES a COPY constructor!
So there was a fallback (?) from the move constructor to the copy constructor.

Why? Does anyone have any idea how does it conform to the C++ standard and what actually is the rule of choosing among copy/move constructors?

like image 468
Sap Avatar asked Jul 11 '14 09:07

Sap


People also ask

What is copy constructor in C?

A copy constructor is a member function that initializes an object using another object of the same class. In simple terms, a constructor which creates an object by initializing it with an object of the same class, which has been created previously is known as a copy constructor.

What is the difference between copy constructor and move constructor?

Move constructor moves the resources in the heap, i.e., unlike copy constructors which copy the data of the existing object and assigning it to the new object move constructor just makes the pointer of the declared object to point to the data of temporary object and nulls out the pointer of the temporary objects.

What does the move constructor do?

A move constructor enables the resources owned by an rvalue object to be moved into an lvalue without copying. For more information about move semantics, see Rvalue Reference Declarator: &&. This topic builds upon the following C++ class, MemoryBlock , which manages a memory buffer.

What is the difference between move constructor and move assignment?

The move assignment operator is different than a move constructor because a move assignment operator is called on an existing object, while a move constructor is called on an object created by the operation. Thereafter, the other object's data is no longer valid.


1 Answers

There is no "fallback". It is called overload resolution. If there are more than one possible candidate in overload resolution then the best match is chosen, according to a complicated set of rules which you can find by reading the C++ standard or a draft of it.

Here is an example without constructors.

class X { };

void func(X &&) { cout << "move\n"; }            // 1
void func(X const &)  { cout << "copy\n"; }      // 2

int main()
{
    func( X{} );
}
  • As-is: prints "move"
  • Comment out "1": prints "copy"
  • Comment out "2": prints "move"
  • Comment out "1" and "2": fails to compile

In overload resolution, binding rvalue to rvalue has higher preference than lvalue to rvalue.


Here is a very similar example:

void func(int) { cout << "int\n"; }      // 1
void func(long) { cout << "long\n"; }    // 2

int main()
{
     func(1);
}
  • As-is: prints "int"
  • Comment out "1": prints "long"
  • Comment out "2": prints "int"
  • Comment out "1" and "2": fails to compile

In overload resolution, an exact match is preferred to a conversion.


In your three examples on this thread we have:

1: Two candidate functions; rvalue prefers rvalue (as in my first example)

A(const A&);
A(A&&);           // chosen

2: Two candidate functions; rvalue prefers rvalue (as in my first example)

A(const A&); 
A(A&&);           // chosen

3: One candidate function; no contest

A(const A&);      // implicitly declared, chosen

As explained earlier, there is no implicit declaration of A(A&&) in case 3 because you have a destructor.

For overload resolution it does not matter whether the function body exists or not, it is whether the function is declared (either explicitly or implicitly).

like image 177
M.M Avatar answered Oct 11 '22 12:10

M.M