3

The following code compiles:

#include <vector>
#include <iostream>
#include <memory>

using namespace std;

class container
{
public:
    container(){}
    ~container(){}
};

class Ship
{
public:
    Ship(){}
    //Ship(const Ship & other){cout<<"COPY"<<endl;}
    //~Ship(){}

    std::unique_ptr<container> up;
};

Ship buildShip()
{
    Ship tmp;
    return tmp;
}

int main(int argc, char *argv[])
{
    return 0;
}

But if we include the user defined destructor ~Ship(){}, the code will only compile if we also include the user defined copy constructor Ship(const Ship & other){cout<<"COPY"<<endl;}

In short:

Compiles:

Ship(){}
//Ship(const Ship & other){cout<<"COPY"<<endl;}
//~Ship(){}

Compiles:

Ship(){}
Ship(const Ship & other){cout<<"COPY"<<endl;}
~Ship(){}

Does NOT Compile:

Ship(){}
//Ship(const Ship & other){cout<<"COPY"<<endl;}
~Ship(){}

Why does the insertion of user defined destructor require an user defined copy constructor and why do we need a copy constructor in the above example at all?

I would expect that there is no copy constructor needed in the example above, as unique_ptr can not even be copied.

newandlost
  • 935
  • 2
  • 10
  • 21
  • https://godbolt.org/g/rFoUWi - clang 3.9.1 does behaves that way here – Ap31 Mar 01 '17 at 15:33
  • Also with clang: Error(s): source_file.cpp:27:12: error: call to implicitly-deleted copy constructor of 'Ship' return tmp;... – newandlost Mar 01 '17 at 15:36
  • yes, all 4 compilers say that the copy constructor is implicitly deleted when you define a destructor – Ap31 Mar 01 '17 at 15:38
  • This is due to `unique_ptr`. `unique_ptr` cannot be copied thus the implicit copy constructor is declared deleted. – 101010 Mar 01 '17 at 15:39

2 Answers2

12

The code gets compiled because buildShip() would use the move constructor automatically generated by the compiler when returning tmp. Adding user-declared destructor prevents the compiler from auto-generating one. E.g., see this or this questions. And the compiler-generated copy constructor can not be used because of the member up which is std::unique_ptr. And copy constuctor of unique_ptr is explicitly deleted.

So this will compile, because the compiler is explicitly asked to generate the move constructor:

class Ship
{
public:
    Ship(){}
    Ship(Ship&&) = default;
    ~Ship(){}
    std::unique_ptr<container> up;
};
Community
  • 1
  • 1
AMA
  • 4,114
  • 18
  • 32
  • Just for making sure that I understand it: Including the user defined destructor prevents the compiler from auto generating a move operator. Because the move operator is missing, return of the buildShip function tries to copy the tmp Ship, thereby calling the default copy constructor, which then tries to copy the tmp, and thereby the unique_ptr, which produces an error. Right? – newandlost Mar 01 '17 at 15:53
  • yes, line `return tmp;` tries to use a copy ctor of `Ship` which is explicitly deleted because of the `unique_ptr` member. – AMA Mar 01 '17 at 15:57
  • Thanks @AMA great answer again! Like you already provided for my previous [question](http://stackoverflow.com/questions/42491544/c-how-to-avoid-access-of-members-of-a-object-that-was-not-yet-initialized). – newandlost Mar 02 '17 at 09:18
0

The idea is that, if the compiler-generated destructor is not good enough for your class, then chances are the copy constructor and copy assignment operator are also not good enough, so the compiler may delete the implicit implementations of those copy operations. Technically, the compiler may still give you implicit copy operations even if you have a user-defined destructor, but that behavior is deprecated in c++11.

See Rule of Three

AFAIK, you still need a copy constructor because buildShip() returns by value.

(However, it's interesting that you are able to compile the versions that use an implicit copy constructor. You shouldn't be able to do that because of the unique_ptr member...)

0x5453
  • 12,753
  • 1
  • 32
  • 61
  • Re your afaik: buildShip returning a value doesn't imply the need for an explicit copy constructor. It's not a special case, it's the same deal as anything else, the implicit one may be fine, or it may not, but that doesn't depend on buildShip. It does use *a* copy constructor, but doesn't necessarily require an *explicit* one. – Jason C Mar 01 '17 at 15:39