Unique_ptr and forward declaration
Asked Answered
S

2

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

Shinleaf answered 6/12, 2014 at 21:31 Comment(13)
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.Mobile
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.Shinleaf
Show us the code that doesn't work?Nashua
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.Shinleaf
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.Fossilize
So the default constructor might destruct the unique_ptr and that's causing the error?Shinleaf
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?Fossilize
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)Shinleaf
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.Fossilize
@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.Mobile
@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.Naturalism
Johannes Schaub's comment on this answer 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."Naturalism
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).Shinleaf
E
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:

Eulaeulachon answered 23/12, 2014 at 16:34 Comment(0)
R
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.

Regine answered 6/12, 2014 at 21:52 Comment(4)
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.Shinleaf
What compiler are you suing?Regine
I'm using the MSVC++ compiler of Visual Studio 2013.Shinleaf
I'm suing the MSVC++ compiler of Visual Studio 2013. I know for a fact @R.MartinhoFernandes is, too..Eulaeulachon

© 2022 - 2024 — McMap. All rights reserved.