views:

188

answers:

7

Below are two fragments (ready to compile) of code. In first fragment in which I'm using only forward declaration for a struct while deleting pointer to this struct from a Base class dtor for a Guest class isn't invoked.
In the second fragment when instead of forward declaration I use full definition of this Guest class using delete in Base works ase intended.
Why? Why does it make a difference? Isn't forward declaration suppose to be just a note for a compiler saying that the definition of this class/struct is somewhere else?
I'm very surprised that it just doesn't work intuitively.

//First just forward dclr  
#include "stdafx.h"
#include <iostream>
using std::cout;

struct Guest;

struct Base
{
    Guest* ptr_;
    Base(Guest* ptr):ptr_(ptr)
    {
        cout << "Base\n";
    }
    ~Base()
    {
        cout << "~Base\n";
        delete ptr_;
    }
};

struct Guest
{
    Guest()
    {
        cout << "Guest\n";
        throw std::exception();
    }
    Guest(int)
    {
        cout << "Guest(int)\n";
    }
    ~Guest()
    {
        cout << "~Guest\n";
    }
};

struct MyClass : Base
{
    Guest g;
    MyClass(Guest* g):Base(g)
    {
        cout << "MyClass\n";

    }
    ~MyClass()
    {
        cout << "~MyClass\n";
    }
};
int _tmain(int argc, _TCHAR* argv[])
{
    try
    {
        Guest* g = new Guest(1);
    MyClass mc(g);
    }
    catch(const std::exception& e)
    {
        std::cerr << e.what();
    }
    return 0;
}

//Second - full def

#include "stdafx.h"
#include <iostream>
using std::cout;

struct Guest
{
    Guest()
    {
        cout << "Guest\n";
        throw std::exception();
    }
    Guest(int)
    {
        cout << "Guest(int)\n";
    }
    ~Guest()
    {
        cout << "~Guest\n";
    }
};

struct Base
{
    Guest* ptr_;
    Base(Guest* ptr):ptr_(ptr)
    {
        cout << "Base\n";
    }
    ~Base()
    {
        cout << "~Base\n";
        delete ptr_;
    }
};



struct MyClass : Base
{
    Guest g;
    MyClass(Guest* g):Base(g)
    {
        cout << "MyClass\n";

    }
    ~MyClass()
    {
        cout << "~MyClass\n";
    }
};
int _tmain(int argc, _TCHAR* argv[])
{
    try
    {
        Guest* g = new Guest(1);
    MyClass mc(g);
    }
    catch(const std::exception& e)
    {
        std::cerr << e.what();
    }
    return 0;
}
+3  A: 

Hi! You can't delete the Guest unless you know its definition. It's destructor won't be called. Also, if Guest has defined a custom operator delete, it would be ignored.

kotlinski
declaration -> definition :)
Armen Tsirunyan
No, a declaration is enough. A forward declaration won't do, though.
kotlinski
@kotlinski - `class X { void f(); };` this is the definition of X whereas `class X;` is the declaration
Armen Tsirunyan
Since f doesn't have any body in your example, X is not fully defined.
kotlinski
@kotilnski: I am afraid you are quite wrong: X is a complete type now. X's definition is there. However X::f is not yet defined, but just declared. A class may be defined even if some of its members are not
Armen Tsirunyan
@kotlinksi: terminology check. This is a class declaration: `class Foo;`, a.k.a a forward declaration. This is a class definition: `class Foo { void foo(); };`. It is also a declaration, because every definition is a declaration, and it contains a declaration of member function `foo`. As Armen says, the class definition completes the type `Foo`. This is a definition of the function `foo`: `void Foo::foo() {}`. It is not part of the definition of the type `Foo`.
Steve Jessop
OK - this is what "The C++ Programming Language" says: "The construct class X { ... }; is called a class definition because it defines a new type. For historical reasons, a class definition is often referred to as a class declaration." Both wrong?
kotlinski
@kotlinksi: C++PL is right. In the sentence you quote, it doesn't mention that `struct Guest;` is *also* a declaration, but is not a definition. So when you say that the declaration is needed, in fact the *definition* is needed. Not all declarations are definitions, so having a declaration is a necessary but not a sufficient condition in this example.
Steve Jessop
+16  A: 

From the C++ standard:

5.3.5/5:

"If the object being deleted has incomplete class type at the point of deletion and the complete class has a non-trivial destructor or a deallocation function, the behavior is undefined."

So you cannot use delete on your incomplete type. It would call the destructor and the compiler is not yet aware of it.

mkaes
+3  A: 

You cannot delete a pointer to an incomplete type. Delete is one of the operations which requires the type to be complete. HTH

Armen Tsirunyan
+2  A: 

The type of ptr_ is incomplete when you invoke delete on it. This leads to undefined behavior. So your destructor may not be called. You can use Boost.checked_delete to avoid such scenarios.

Naveen
+2  A: 

(The stdafx.h header is not standard c++.) If I compile with g++, the compiler generates:

 warning: possible problem detected in invocation of delete operator:
 warning: invalid use of incomplete type ‘struct Guest’
 warning: forward declaration of ‘struct Guest’
 note: neither the destructor nor the class-specific operator delete will be called, even if they are declared when the class is defined.

Configure your compiler to compile at proper warning and error levels.

Jan
int _tmain(int argc, _TCHAR* argv[]) <- this is also not standard
VJo
+2  A: 

Informally: the compiler needs the class definition in order to delete the object correctly, because it needs to know how to call the destructor and/or operator delete for that class.

Formally, 5.3.5/5:

If the object being deleted has incomplete class type at the point of deletion and the complete class has a non-trivial destructor or a deallocation function, the behavior is undefined.

You'd be OK if (for example) Guest was POD, but you gave it a destructor, so you're not OK.

Steve Jessop
+1  A: 

This may be useful.

Chubsdad