3

Let's say that I have this object:

struct foo {
    std::unique_ptr<int> mem;
    virtual ~foo() = default;
};

I can no longer return a foo object created in a function:

foo make_foo() {
    foo result;

    result.mem = std::make_unique<int>({});
    return result;
}

As may be indicated I need the destructor to be virtual because this will be a base class. But even if I'm using the default destructor, that's not enough, I still can't return an object created in a function.

I get the error:

error C2280: foo::foo(const foo &): attempting to reference a deleted function

Is there a way to navigate around this issue?

NathanOliver
  • 171,901
  • 28
  • 288
  • 402
Jonathan Mee
  • 37,899
  • 23
  • 129
  • 288
  • 1
    Try adding `foo() = default;`, `foo(foo &&) = default;` and `foo &operator=(foo &&) = default;`. Move constructor will not be generated implicitly if you have defined move assignment operator copy constructor/assignment operator or destructor. –  Sep 27 '18 at 16:20
  • 1
    @user463035818 The very last error message is */usr/include/c++/6/bits/unique_ptr.h:359:7: note: declared here unique_ptr(const unique_ptr&) = delete;* – NathanOliver Sep 27 '18 at 16:22
  • @NathanOliver oh right, I am too much used to fix first things first so i overlooked it – 463035818_is_not_an_ai Sep 27 '18 at 16:23

2 Answers2

7

Per [class.copy.ctor]/8

If the definition of a class X does not explicitly declare a move constructor, a non-explicit one will be implicitly declared as defaulted if and only if [...]

  • X does not have a user-declared destructor.

Since

virtual ~foo() = default;

is a user-declared destructor you no longer have a move constructor so it tries to use the copy constructor but can't because that is deleted as you have a non-copyable member.

To get the move constructor back, and to keep the default constructable, you need to add

foo() = default;
foo(foo&&) = default;
foo &operator=(foo &&) = default; // add this if you want to move assign as well

to foo


The reason you have to add foo() = default; when you add foo(foo&&) = default; is that foo(foo&&) = default; is a used-declared constructor and if you have any user-declared constructors then the default constructor is no longer provided.


This is a "hack" but what you could do is move the virtual destructor into another class and then inherit from that. That will give you a virtual destructor in foo without having to declare it and give you the default constructors you want. That would look like

struct make_virtual
{
    virtual ~make_virtual() = default;
};

struct foo : make_virtual {
    std::unique_ptr<int> mem;
};
Community
  • 1
  • 1
NathanOliver
  • 171,901
  • 28
  • 288
  • 402
  • I do keep forgetting that defaulting a destructor is *not* the same as the default destructor. There's no other way to make the default destructor virtual is there? – Jonathan Mee Sep 27 '18 at 16:33
  • @JonathanMee No. C++ really makes you pay for it the first time (the base class). In the derived classes `~derived()` will be implicitly virtual. – NathanOliver Sep 27 '18 at 16:35
  • @JonathanMee Actually I have just added a "hack" for you. – NathanOliver Sep 27 '18 at 16:39
  • Just another note here, in my C++14 compiler, defaulting the move constructor only solves the error if I change the `return` statement of `make_foo` to: `return move(result)` I would have thought that should have worked automatically? Should I have to be doing this `move`? – Jonathan Mee Sep 27 '18 at 16:43
  • 1
    @JonathanMee What compiler and version are you using? [Per this](https://stackoverflow.com/questions/25875596/can-returning-a-local-variable-by-value-in-c11-14-result-in-the-return-value-b) `result` should be treated as an rvalue since it is a local automatic object. – NathanOliver Sep 27 '18 at 16:46
  • Curses nope, it's just cause my locally constructed variable was a `foo&&` once I fixed that it moved properly. – Jonathan Mee Sep 27 '18 at 16:57
  • 1
    @JonathanMee Does this answer everything for you then? – NathanOliver Sep 27 '18 at 16:59
  • Yup you got it all. Thank you so much. I'll accept after letting it mellow for a bit... – Jonathan Mee Sep 27 '18 at 17:01
1

Either provide your own copy constructor and a default constructor, convert the member to a shared pointer or provide a move constructor. One possible solution:

struct foo {
    unique_ptr<int> mem;
    foo() = default;
    foo(const foo& copy);
    virtual ~foo() = default;
};

foo::foo(const foo& copy) : mem(new int(*copy.mem)) {}

foo make_foo() {
    foo result;
    result.mem = make_unique<int>();
    return result;
}
Ron
  • 14,674
  • 4
  • 34
  • 47