views:

102

answers:

7

Consider the following:

In X.h:

class X
{
    X();
    virtual ~X();
};

X.cpp:

#include "X.h"

X::X()
{}

Try to build this (I'm using a .dll target to avoid an error on the missing main, and I'm using Visual Studio 2010):

Error 1 error LNK2001: unresolved external symbol "private: virtual __thiscall X::~X(void)" (??1X@@EAE@XZ)

Small modifications result in a successful build, however:

X.h:

class X
{
    inline X(); // Now inlined, and everything builds
    virtual ~X();
};

or

X.h:

class X
{
    X();
    ~X(); // No longer virtual, and everything builds
};

What causes the unresolved external in the linker when the .dtor is virtual or when the .ctor isn't inlined?

EDIT:

Or, perhaps more interestingly, why do I not get an unresolved external if I make the destructor non-virtual, or if I inline the constructor?

A: 

You need to give a body to the virtual destructor:


class X
{
    X();
    virtual ~X() {}
};
Dima
Thank you for the suggestion, but this isn't a plz-send-teh-codez how-do-i-get-rid-of-the-error question. Rather it's a question about understanding the tool that we all make a living with a bit more closely.
Greg D
+1  A: 

These aren't a complete program yet (or even a complete DLL). When you are getting the error, you are actually being helped, because X is unusable without a definition for ~X()

All it means is that this specific compiler instance needed a definition for it in some cases. Even if it compiles, it doesn't do anything.

Lou Franco
This is a distilled sample asking about a particular behavior that was observed. I'm not interested in making it a complete program, I'm interested in understanding why it builds and doesn't build in the observed situations.
Greg D
It doesn't build because of compiler-specific peculiarities. It probably should have because you never destroy an X, and it also could have just pruned all of the code out since it's never called. But, it didn't. The class as written isn't meaningful, so it's ok for it not to compile.
Lou Franco
A: 

You may be getting away with this because both constr and destr are private - if there is no other ref to class X in your build then the compiler may be deducing that the destr is not required, so lack of a definition is no biggie.

This does not explain to me why case 1 fails while 2 and 3 build OK though. Wonder what happens if both are made public?

Steve Townsend
I see Martin York has explained this perfectly above.
Steve Townsend
+9  A: 

Situation 1:

You have the code for the constructor.
So it builds the constructor into the object file. The constructor needs the address of the destructor to put into the virtual table because it can not find it the constructor can not be built.

Situation 2: (inline constructor)

The compiler decides it does not need to build the constructor (as it will be inlined).
As such it does not plant any code and therefore does not need the address of the destructor.

If you instanciate an object of type X it will again complain.

Situation 3: (non virtual destructor)

You don't need the address of the destructor to build the constructor.
So it does not complain.

It will complain if you instantiate an object of type X.

Martin York
Excellent. Again.
Johann Gerell
A: 

The answer to your first question,

What causes the unresolved external in the linker when the .dtor is virtual or when the .ctor isn't inlined?

...is, quite simply, that you don't have a definition for the destructor.

Now your second question is somewhat more interesting:

why do I not get an unresolved external if I make the destructor non-virtual, or if I inline the constructor?

And the reason is because your compiler didn't need X's destructor since you never instantiated X, so it threw your whole class away. If you try to compile this program, you will get an unresolved external:

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

X::X() {};

int main()
{
    X x;
    return 0;
}

But if you comment out X x; it will compile just fine, as you have observed.

Now let's come back around to why it won't compile if the destructor if virtual. I'm speculating here, but I believe the reason is because, since you have a virtual destructor, X is now a polymorphic class. In order to lay-out polymorphic classes in memory, compilers that implement polymorphism using a vtable need the adresses to every virtual function. You haven't implemented X::~X, so an unresolved external results.

Why doesn't the compiler just throw X away as it did when X was not a polymorphic class? More speculation here. But I expect the reason is because even if you haven't directly instantiated X, it can't be sure that nowhere in your code does an X live, masqerading as something else. For an example, consider an abstract base class. In this case, you'll never instantiate Base directly and the code for Derived might be in a totally seperate translation unit. So when the compiler gets to this polymorphic class, it can't discard it even if it doesn't know you instantiated it.

John Dibling
+2  A: 

In C++ functions have to be defined if and only if they are used in your program (see the ODR in 3.2/2). In general, non-virtual functions are used if they are called from potentially evaluated expressions. Any non-pure virtual function is considered unconditionally used. When [non-virtual] special member functions are used is defined in dedicated locations of the language standard. And so on.

  • In your first example, you declared your destructor as a non-pure virtual function. This immediately means that your destructor is used in your program. This, in turn, means that the definition of that destructor is required. You failed to provide a definition, so the compiler reported an error.

  • In your third example the destructor is non-virtual. Since you are not using the destructor in your program, no definition is required and the code compiles (see 12.4 for detailed description of what constitutes a use of a destructor).

  • In your second example you are dealing with a quirk of the implementation, triggered by the fact that the constructor is inlined. Since the destructor is non-pure virtual, the definition is required. However, your compiler failed to detect the error, which is why the code seems to compile successfully. You can dig for the reasons of this behavior in the details of the implementation, but from the C++ point of view this example is as broken as the first one for exactly the same reason.

AndreyT
+1  A: 

I have a suspicion on this one that this is implementation defined behavior. Here's why

$10.3/8- "A virtual function declared in a class shall be defined, or declared pure (10.4) in that class, or both; but no diagnostic is required (3.2)."

GCC gives error such as below, which again, is highly suggestive (to me at least) about a non-standard implementation detail of implementing virtual functions

/home/OyXDcE/ccS7g3Vl.o: In function X::X()': prog.cpp:(.text+0x6): undefined reference tovtable for X' /home/OyXDcE/ccS7g3Vl.o: In function X::X()': prog.cpp:(.text+0x16): undefined reference tovtable for X' collect2: ld returned 1 exit status

I am confused if a diagnostic is really required from a compiler for the OP code, so thought of posting this, even as I risk downvotes :). Of course, a good compiler should I guess.

Chubsdad