Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to return an object with no copy constructor

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?

like image 991
Brian Moths Avatar asked Jan 07 '15 07:01

Brian Moths


People also ask

What happens if there is no copy constructor in the class?

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.

What happens if there is no copy constructor in C++?

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.

Is copy constructor necessary?

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).

Why there is no copy constructor in Java?

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.


Video Answer


1 Answers

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 defaulting or deleteing 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

like image 164
Praetorian Avatar answered Sep 25 '22 02:09

Praetorian