My questions concerns how to return an object that does not have a copy constructor. As an example let's imagine that I have some bigResource
that sits in the heap, and let's say I keep track of it using a unique_ptr
. Now suppose I give ownership of this resource to a caterpillar. Then I have a CaterpillarWithBigResource
. Now at some point, this CaterpillarWithBigResource
is going to turn into a ButterflyWithBigResource
, so the Caterpillar
object will have to transfer ownership to the Butterfly
object.
I have written the following code to model the situation:
#include <cstdlib>
#include <iostream>
#include <memory>
class ButterflyWithBigResource {
public:
// If I uncomment just this line, I get an error
// ButterflyWithBigResource(const ButterflyWithBigResource& other) = default;
// If I uncomment just this line, I get an error
// ButterflyWithBigResource(const ButterflyWithBigResource& other) = delete;
// With both above lines commented out, I get no errors, and the program runs fine.
ButterflyWithBigResource(std::unique_ptr<int>&& bigResource) :
bigResource(std::move(bigResource)) {
}
const int& getResource() {
return *bigResource;
}
private:
std::unique_ptr<int> bigResource;
};
class CaterpillarWithBigResource {
public:
CaterpillarWithBigResource(int bigResource) :
bigResource(new int(bigResource)) {
}
ButterflyWithBigResource toButterfly() && {
return ButterflyWithBigResource(std::move(bigResource));
}
private:
std::unique_ptr<int> bigResource;
};
/*
*
*/
int main(int argc, char** argv) {
CaterpillarWithBigResource caterpillarWithBigResource(5);
ButterflyWithBigResource butterflyWithBigResource(std::move(caterpillarWithBigResource).toButterfly());
std::cout << butterflyWithBigResource.getResource() << std::endl;
return 0;
}
Notice that neither the Caterpillar
nor the Butterfly
have default copy constructors, because they each have a unique_ptr
. However, I wouldn't expect this to be problem, so only move constructors should necessary. After all, I am only transferring ownership from the Caterpillar
to the Butterfly
.
In fact when I compile the program with g++ -c -g -std=c++11 -MMD -MP -MF
using g++
version 4.8.2
it works just fine.
But now the weird thing is if I remind the compiler that the Butterfly
's copy constructor is deleted by adding the line ButterflyWithBigResource(const ButterflyWithBigResource& other) = delete;
, the program no longer compiles, and the compiler complains that copy constructor is deleted, so that I can't return the Butterfly
in the toButterfly
method.
If I then try to tell it that everything is ok by instead having the line ButterflyWithBigResource(const ButterflyWithBigResource& other) = default;
, I again get the same error.
What I want to happen is for the Butterfly
constructed in the toButterfly
method to be moved to toButterfly
's return address, and then later used as the argument of Butterfly
's move constructor when constructing butterflyWithBigResource
in main()
. Is there a way to make this happen?
If no user-defined copy constructors are provided for a class type (struct, class, or union), the compiler will always declare a copy constructor as a non-explicit inline public member of its class.
If there is no copy constructor defined for the class, C++ uses the default copy constructor which copies each field, ie, makes a shallow copy.
A user-defined copy constructor is generally needed when an object owns pointers or non-shareable references, such as to a file, in which case a destructor and an assignment operator should also be written (see Rule of three).
In C++ that statement makes a copy of the object's state. In Java it simply copies the reference. The object's state is not copied so implicitly calling the copy constructor makes no sense. And that's all there is to it really.
When you comment out both the lines that explicitly default
and delete
the copy constructor, the compiler is free to implicitly generate a move constructor (and move assignment operator) for you.
By explicitly default
ing or delete
ing the copy constructor, you suppress the implicit generation of the move constructor.
From N3337, §12.8/9 [class.copy]
If the definition of a class
X
does not explicitly declare a move constructor, one will be implicitly declared as defaulted if and only if
—X
does not have a user-declared copy constructor,
—...
When the move constructor is no longer generated, the return value from toButterfly()
must be copied, but that fails regardless of whether you've defaulted or deleted the copy constructor.
In the case where you default
the copy constructor, the compiler is unable to generate a default copy constructor implementation because of the presence of the unique_ptr
data member (which is not copyable).
When you delete
the copy constructor, if it gets selected via overload resolution, that is an error.
You shouldn't have to explicitly delete the copy constructor since, as explained above, the presence of the unique_ptr
data member implicitly deletes it, but if you want to do that, then you'll need to also explicitly default the move constructor (and the move assignment operator too, if you want move assignment to work)
ButterflyWithBigResource(ButterflyWithBigResource&&) = default;
ButterflyWithBigResource& operator=(ButterflyWithBigResource&&) = default;
Live demo
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