Weird Behaviour of VC++ .NET 2003?

Started by
6 comments, last by SiCrane 19 years, 4 months ago
Consider the following classes:

class A {
    virtual void func() = 0;
};

class B : public A {
    B()
    {
        func();
    }
};

class C : public B {
    C() : B()
    {
    }

    virtual void func()
    {
        cout << "Hi" << endl;
    }
};

int main()
{
    C* Obj = new C();
    delete Obj;
}
Note especially the call to "func()" in B's constuctor. My understanding is that when the new instance of C gets created, B's constructor will call C::func()... ie. as a virtual function should. This is what happens in C++ Builder 6... However in Visual C++ .NET 2003, the above code will get a LINKER error, saying that A::func() is an unreferenced external! Changing the call in B's constructor to an explicit polymorphic "this->func()" makes no difference. Now here comes the fun... if I do go ahead and define "func" in B, it will compile, but B's version of func() will be called instead of C's. Clearly VC++ is not treating it as a correctly polymorphic function when called from the constructor. However, if I make the same call from any other function in B (not the constuctor), it works just fine, and as expected. Am I missing something? Is there a reason why a polymorphic call can't be made from the constructor in VC++? Is this the expected behaviour, and C++ Builder just works regardless of the expectations? Has anyone else encountered this?
Advertisement
The C++ standard states that when you call a virtual function in a constructor, the binding of the virtual function will be the current class's version.

So when B() calls func(), it first looks for B::func(). However, B has not defined a func() so, it tries to grab A's func(). As you noticed, this causes a linker error since you never implement A's func().

Basically MSVC 7.1 implements this correctly, and your version of C++ Builder apparently does not.
Fair enough - answers my question :)

I actually prefer it being treated like a virtual function still, and I'm not sure why they decided otherwise... it's always possible to use the scope operator to call a different version. With these sematics, moving code between any "init"-type functions and constructors could result in different code being executed: some potentially very hard to track down bugs!

Either way, thanks for the info. Good to know.
Basically the reason is that functions should be able to expect all the member variables to be in a valid state. If the virtual function call executed in the constructor invoked a version of a virtual function found in a child class, then it may reference variables that are not in a sane state.

Going with your example, when the B() constructor in your example is called none of the member variables in C have be constructed yet. What happens if C::func() is called, and C::func() needs to use some of the member variables declared in C? For example, what if allocates new memory and assigns it to a member variable declared in C? When the C() constructor is executed after the B() constructor finishes, it might initialize that pointer, resulting in a memory leak.
Yeah that's a fair point. Almost 100% of the time when I do it, it's calling some sort of function at the END of the constructor, but I can see the logic now. Thanks for the clarification.

One question though: if you call a non-virtual function from the constructor, which then goes and calls a virtual function (ex. "func()"), will it be called in the tradition vtable manner or the "current class"? I would hope the latter, or else the protection for non-fully-constructed objects seems rather sparse. Still, I can see some cases where it would be unexpected behaviour in either case.

Hopefully they develop some sort of sane behaviour for cases with exceptions being thrown from constructors soon ;)

Anyways thanks again for clearing that up for me.
All virtual functions called, directly or indirectly, during the construction of an object of an object should be a version defined in the class of the constructor being called, or one of the bases.

Usually this is implemented by the vptr not being updated to point to the derived class's vtable until after the base class's constructor has been executed. So the virtual functions are being called in the tradition vtable manner, but the vtable being used is just different than the one it would see after construction is finished.

But what do you think isn't sane about the behaviour of exceptions thrown during construction?
Well the exact situation here was that I have an application framework that could be implemented by any number of derived classes. The actual application (another derived class from a specific implementation) can handle various events by overloading virtual functions. This is a pretty standard layout.

In this case, the window "reshape" event really needs to be called right after initialization (constructor), and this is also pretty standard behaviour. The problem is that this event does not get passed down the object heirarchy for the reasons that you described. In this case it wasn't much of a problem because I could simply move the call to another initialization function and all was good.

Regarding exceptions thrown in the constructor, the problem is the cleanup. While many compilers currently implement a sort of "feature" to call the destructor of stack objects that go out of scope, nothing like this exists for heap objects to my knowledge. For example:

Obj *a;try{    a = new Obj();     // throws an exception}catch(...){    // a is now "partially constructed"}


There are more serious problems too, but the above example is already tough: can you do a "delete a;"? Usually that will work. However, should the DEstructor for a be called? It's hard to say, since a hasn't been fully constructed... different compilers treat this situation differently in my experience.

Anyways the best way to avoid the above is to simply avoid any operations that could produce exceptions in constructors, and instead have "init"-type functions. I'm not really a fan of that, but occasionally I have to use it.
The standard says that if an exception is thrown during the execution of a constructor, the stack is unwound and the destructor for all member variables and base classes are called. If an exception is thrown during the construction of a member variable, all previous member variable that have been constructed will be destroyed and the destructor for any applicable base classes will be called. If your compiler does not unwind the stack or destroy the member variables or base classes then the compiler is not compliant.

Therefore calling delete on the pointer is not necessary, and also not safe.

The only place where resources would leak is if in the constructor you have unrecoverable resources on the stack, like a newed object in a dumb pointer. Of course you shouldn't be writing code like that in the first place.

This topic is closed to new replies.

Advertisement