17

Say I have two classes:

"Foo.h"

#pragma once    
class Foo
{
public:
    Foo()
    {

    };

    ~Foo()
    {

    };
};

"A.h"

#pragma once
#include <memory>

class Foo;

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

    std::unique_ptr<Foo> foo;
};

A holds a unique_ptr of Foo. I didn't want to include Foo in "A.h", so I forward declared it. By just forward declaring the class Foo in "A.h", I get a compile time error:

error C2027: use of undefined type 'Foo'
error C2338: can't delete an incomplete type  

So I was following this article on how to avoid this error and moved A's destructor in it's own .cpp file where I also include Foo:

"A.cpp"

#include "A.h"

#include "Foo.h"

A::A()
{

}

A::~A()
{

}

After implementing the destructor of A in "A.cpp", I'm able to compile the program, because the class Foo is known in "A.cpp". This seems logical, because unique_ptr needs the complete type to call it's destructor. But to my surprise, after commenting out the constructor of A (in "A.h" as well as "A.cpp"), I get the same error. How is this possible? Why does the compiler complain about beeing not able to call Foo's destructor when A has no constructor?

EDIT: I uploaded the 4 files so you can test the program. I'm using MSVC++ of Visual Studio 2013.

http://www.filedropper.com/test_61

abcheudg234
  • 179
  • 1
  • 1
  • 4
  • 5
    `A` does have a constructor when you comment out your constructor: a default constructor is provided by the compiler, and this constructor gets an inline definition. – dyp Dec 06 '14 at 21:32
  • Yes, but with the standard constructor I get the compiler error that he can't delete an in complete type. When writing my own empty constructor, that should look the same like the default one, I don't get these compiler errors. – abcheudg234 Dec 06 '14 at 21:40
  • Show us the code that doesn't work? – Alan Stokes Dec 06 '14 at 21:44
  • You just have to comment out the constructor in A.h and A.cpp and the code won't compile - but you have to actually instantiate an object of A somewhere. – abcheudg234 Dec 06 '14 at 21:54
  • 1
    When you comment out the constructor the compiler will define it implicitly, as an inline function (i.e. in the header not in the .cpp file). It appears as though your compiler thinks the implicitly-defined default constructor might want to destroy the `unique_ptr`, possibly in case the body of the constructor (or another member) throws an exception and the unique_ptr member needs to be destroyed again. The code you showed above should not have that effect, because there are no other members. – Jonathan Wakely Dec 06 '14 at 22:00
  • So the default constructor might destruct the unique_ptr and that's causing the error? – abcheudg234 Dec 06 '14 at 22:05
  • 1
    In the code above, the default constructor should not need to destroy it (so if the compiler thinks it needs to that's a compiler bug) but if your real code has other members in the class defined after the `unique_ptr` member, and constructing one of those members can fail with an exception, the unique_ptr member would need to be destroyed. Are you showing the **exact** code you're testing, or a simplified version that doesn't actually give the same error? – Jonathan Wakely Dec 06 '14 at 22:08
  • I'm showing the exact same code, only main.cpp is missing. I uploaded the 4 files and added comments so you can reproduce the error. (See initial post) – abcheudg234 Dec 06 '14 at 22:22
  • 2
    Oops, I'm wrong, the constructor does need to know the complete type, for exactly the reasons Chris Drew gives below. So the constructor needs to be defined non-inline along with the destructor. – Jonathan Wakely Dec 06 '14 at 22:46
  • @JonathanWakely Looking at clang++'s error message, I do think you're right with your guess: The completeness of `Bar` is required because the destructor of the `unique_ptr` is instantiated. But I do not understand why this instantiation takes place. It seems unnecessary, considering `Foo` has no bases nor other data members. – dyp Dec 06 '14 at 23:38
  • @JonathanWakely Actually, maybe I'm wrong. I've encountered this error myself and that was how I rationalised it to myself but, as dyp points out, it doesn't quite add up. – Chris Drew Dec 06 '14 at 23:57
  • Johannes Schaub's comment on [this answer](http://stackoverflow.com/a/6089065/3422652) appears relevant: "A class constructor will reference the destructors of its members (for the case where an exception is thrown, those destructors need to be called). So while unique_ptr's destructor needs a complete type, it is not enough to have a user defined destructor in a class - it also needs a constructor." – Chris Drew Dec 07 '14 at 12:32
  • 2
    So a class ALWAYS needs the complete type for both it's constructor and destructor when using unique_ptr? I'm a litte confused because all articles you find on the web just mention the need of the full type in the destructor (like in the article I postet in my initial question). – abcheudg234 Dec 09 '14 at 13:13

2 Answers2

24

The constructor needs access to the deleter in just the same way the destructor does: exception safety requires that the constructor be able to roll-back initialisation of all the members in the case that your constructor's body throws:

[C++14: 12.6.2/10]: In a non-delegating constructor, the destructor for each potentially constructed subobject of class type is potentially invoked (12.4). [ Note: This provision ensures that destructors can be called for fully-constructed sub-objects in case an exception is thrown (15.2). —end note ]

Related:

Community
  • 1
  • 1
Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055
0

It is no possible for 'A' to not have a constructor.

If you comment the constructor you wrote, the compiler will make a default constructor for you and it won't necessarily be in the same place you defined the one you made. Causing said problem.

solid.py
  • 2,782
  • 5
  • 23
  • 30
senex
  • 447
  • 3
  • 14
  • 1
    But why do I have to define the constructor in A.cpp at all? I understand that the compiler complains that A's destructor has to be defined somewhere where Foo.h is included - so that it is a complete type and A's destructor can actually destruct Foo. But I don't get why A's CONstructor also has to be defined in A.cpp. You can try the code by copying the classes and instantiating A in main(). When commenting out A's constructor, the code won't compile. – abcheudg234 Dec 06 '14 at 21:59
  • 3
    What compiler are you suing? – senex Dec 06 '14 at 22:02
  • 1
    I'm using the MSVC++ compiler of Visual Studio 2013. – abcheudg234 Dec 06 '14 at 22:06
  • 2
    I'm suing the MSVC++ compiler of Visual Studio 2013. I know for a fact @R.MartinhoFernandes is, too.. – Lightness Races in Orbit Dec 23 '14 at 17:49