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?
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.
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.
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.
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.
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{} );
}
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);
}
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).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With