0

I have a header file (say the_foo.h) which defines/declares the following classes:

// \file the_foo.h

class FooBase
{
    virtual size_t bar_size() = 0;
};

template<class Bar>
class Foo : public FooBase
{
    size_t  bar_size()  { return sizeof(Bar); }
};

class TheBar;       // TheBar forward declaration

class TheFoo : public Foo<TheBar>
{
};

Compiling with MS vc14 (Visual Studio 2015), I notice the following behaviors:

  1. Any cpp file which includes the_foo.h and defines TheBar, eg:

    #include "the_foo.h" class TheBar {}; // TheBar definition

    will compile just fine. However,

  2. Any cpp file which includes the_foo.h and DOES NOT DEFINE TheBar, eg:

    #include "the_foo.h"

    fails to compile with error: the_foo.h(11): error C2027: use of undefined type 'TheBar'

    The cpp file compiled above contains one single line: inclusion of the header, no more code.
    Removing the virtual member function declaration virtual size_t bar_size() = 0; does fix the error.
    Thanks to answer from Sam Varshavchik, this code compile fine using gcc 5.3.1, so this issue is clearly compiler specific.

My questions are:

  • Why does compilation fail in case (2), when TheBar is forward declared only ?
  • Is there any way to make case (2) compiling successfully under vc14, i.e. without an explicit definition of TheBar, which class I need to keep opaque in some cpp files ?

PS: I edited the code sample of this question in order to make clear what actually causes the issue described in the question. The original code sample (which is quoted in the answer from Sam Varshavchik) may indeed have misleaded on the actual cause of the issue and, consequently, leaded to answers and comments out of the question scope.

shrike
  • 4,449
  • 2
  • 22
  • 38
  • What you trying to do here is to create and delete forward declared class. Compiler does not know about constructor or destructor. So it should not work. Think of better object managing system if you need to hide the declaration. – Teivaz Apr 12 '16 at 11:32
  • http://stackoverflow.com/questions/4325154/delete-objects-of-incomplete-type – eerorika Apr 12 '16 at 11:33
  • @teivaz: as far as I understand C++, I do not try to create anything in my example: I just include the_foo.h header file and do not instanciate anything; if I actually did instanciate TheFoo without having defined TheBar, compiler would have failed at the Foo() constructor. I my example, compiler fails at TheFoo destructor, not at the constructor. – shrike Apr 12 '16 at 11:44
  • @SharpDressedMan, the compiler here tries to generate class with all its functions. The destructor according to RAII will call the destructor which is not defined for the forward declared class. – Teivaz Apr 12 '16 at 11:49
  • And by the way following code will crash after leaving the scope: `TheFoo f1, f2; f1 = f2;`. You need to define proper copy constructor or forbid it. – Teivaz Apr 12 '16 at 11:52
  • @teivaz: OK, but why does compiler try to do this ? (I would have understood if I had only one instance of TheFoo) And why is error fixed by removing the 'virtual' qualifier of FooBase destructor ? – shrike Apr 12 '16 at 12:27
  • @SharpDressedMan When you have virtual destructor compiler should generate a table of virtual functions and will generate the code for virtual destructor. But when it is non-virtual the compiler will inline it (defer the code generation) and if you never call it it will never generate the code – Teivaz Apr 12 '16 at 13:26
  • @teivaz: you certainly are right but, still, why does vc14 tries to generate the table though there is neither instance nor reference to any TheFoo object ? while gcc has no problem and generate no error with the same code. – shrike Apr 12 '16 at 13:59
  • @SharpDressedMan I don't know the details of this specific compiler and when it comes to compiler specific behavior (especially when it's not related to the new features) you most likely you are having undefined behavior. Unfortunately I can't recall what standard says about it atm – Teivaz Apr 12 '16 at 20:36

2 Answers2

1

Your test case compiles without issues with gcc 5.3.1:

$ cat t.C
class FooBase
{
public:
    FooBase()          {}
    virtual ~FooBase() {}
};

template<class Bar>
class Foo : public FooBase
{
public:
    Foo()   { sizeof(Bar); bar = new Bar; }
    ~Foo()  { sizeof(Bar); delete bar; }
    Bar*    bar;
};

class TheBar;

class TheFoo : public Foo<TheBar>
{
public:
    TheFoo();
    ~TheFoo();
};
[mrsam@octopus tmp]$ g++ -c -o t.o t.C
[mrsam@octopus tmp]$ 

The answer here appears to be "you're using an old C++ compiler that does not correctly compile this".

Sam Varshavchik
  • 114,536
  • 5
  • 94
  • 148
  • I am using MS VC14 (VS 2015) compiler which I expected to be not too old though... :-) – shrike Apr 12 '16 at 11:14
  • 1
    This cannot work. The compiler has no idea how to delete bar. – Chiel Apr 12 '16 at 11:28
  • @Chiel: the compiler has no idea either how to construct bar at line just above within the Foo constructor; compiler does not report any error from this line though. – shrike Apr 12 '16 at 11:34
  • That is true, it is even worse than I though :) I thought that the constructor and destructor of `TheFoo` were implemented, just like the base, but I did not look properly. – Chiel Apr 12 '16 at 11:36
0

The compiler searches for a definition of the class TheBar when you try to construct or delete it in the constructor and destructor of the Foo class. It means it needs the implementation at that point, otherwise it has no idea what to do.

If make the following example:

The first header:

// foobase.h

class FooBase
{
    public:
        FooBase()          {}
        virtual ~FooBase() {}
};

template<class Bar>
class Foo : public FooBase
{
    public:
        Foo()  { bar = new Bar; }
        ~Foo() { delete bar; }
    private:
        Bar* bar;
};

class TheBar;

class TheFoo : public Foo<TheBar>
{
    public:
        TheFoo() {};
        ~TheFoo() {};
};

The next header

// thebar.h

class TheBar {};

And the following main file:

// foo_main.cxx

// #include "thebar.h" // Including this include makes the program run
#include "foobase.h"

int main()
{
    TheFoo thefoo;
    return 0;
}

Then your compiler will tell you what is wrong (only first error shown):

./foobase.h:14:32: error: allocation of incomplete type 'TheBar'
        Foo()  { bar = new Bar; }

Including thebar.h will solve the problem.

Chiel
  • 6,006
  • 2
  • 32
  • 57
  • the compiler has no idea either how to construct bar at line just above within the Foo constructor; compiler does not report any error from this line though. – shrike Apr 12 '16 at 11:35
  • Indeed, and the compiler does show an error, which you will find if you compile the example that I put here. – Chiel Apr 12 '16 at 11:47
  • For sure, in your example, compiler obviously fails because you try to instanciate TheFoo without TheBar definition. In my example, nothing is instanciated at all (as far as I understand), this seems to be totally different from your example. More, in my example, if I remove the 'virtual' qualifier from FooBase destructor, my example compiles with no error in both (1) and (2) cases. – shrike Apr 12 '16 at 11:53
  • If you do not define `TheFoo` ever after its declaration (thus remove the curly braces), and you never call it, then indeed your code should compile, but then you can just as well compile empty files. – Chiel Apr 12 '16 at 11:58
  • `otherwise it has no idea what to do` This is not exactly correct in the case of the destructor. The compiler does have an idea. It knows that the destructor must be trivial and therefore no destructor call needs to be made, only memory needs to be deallocated. But if the destructor is not trivial, then the program is ill formed, of course. – eerorika Apr 12 '16 at 12:01
  • @user2079303 I was referring to this specific example, where the `delete bar` is called in the destructor `Foo` class. – Chiel Apr 12 '16 at 12:07
  • @Chiel: off course, TheFoo is defined somewhere in one cpp file; what I need here is to include the_foo.h in another cpp file, where TheFoo must remain opaque and has not to be defined thus... your answer is outside scope of the question, sorry. – shrike Apr 12 '16 at 12:11
  • @Chiel yes, that's what I was referring to. The compiler knows what to do. It's perfectly OK, as long as the destructor is trivial. But it's not OK (it's UB), if the destructor is not trivial. – eerorika Apr 12 '16 at 12:14
  • @user2079303: correct me if I am wrong: as long as I do not try to delete a TheFoo object in a cpp file not including TheBar definition, there is no UB, is there ? In my code, the TheFoo objects are actually instanciated and deleted in a cpp where TheBar is actually properly defined. – shrike Apr 12 '16 at 12:22
  • @SharpDressedMan correct. The UB I referred to only occurs when the destructor is called. `sizeof(Bar)` on the other hand is something that can prevent compilation for anyone who instantiates `Foo` with an incomplete template argument. – eerorika Apr 12 '16 at 12:54
  • @user2079303: sure, sizeof() is inserted for test purpose only; anyway, instanciation of TheFoo would fail if Bar is incomplete because TheFoo constuctor tries to create a Bar. – shrike Apr 12 '16 at 13:04
  • @all: just to proove the question I asked is not related at all to the destruction of bar in `Foo`. Here is a modification of `FooBase` and `Foo<>` which generate the same error: `class FooBase { virtual size_t bar_size() = 0; }; template class Foo : public FooBase { size_t bar_size() { return sizeof(Bar); } };` – shrike Apr 12 '16 at 14:12