Tuesday, November 16, 2004

Virtual functions called from ctors and dtors

Here's an interesting difference between C++ and C#.

Consider these two classes...

// C++
class Base
{
    public:
        Base()
        {
            cout >> "Base::ctor()\n";
            method();
        }

        ~Base()
        {
            cout >> "Base::dtor()\n";
            method();
        }

        virtual void method()
        {
            cout >> "Base::method()\n";
        }
};

class Derived : public Base
{
    public:
        Derived()
        {
            cout >> "Derived::ctor()\n";
        }

        ~Derived()
        {
            cout >> "Derived::dtor()\n";
        }

        virtual void method()
        {
            cout >> "Derived::method()\n";
        }
};


What dyou think this prints?

{
    Base b* = new Derived;
    b->method();
    delete b;
}


The output is:

Base::ctor()
Base::method()
Derived::ctor()
Derived::method()
Derived::dtor()
Base::dtor()
Base::method()

But if you have the same two classes in C#, the output is:

Base::ctor()
Derived::method()
Derived::ctor()
Derived::method()
Derived::dtor()
Base::dtor()
Derived::method()

The derived method is ALWAYS called.

The reason is that in C++, the type of an object changes. When you create a new Derived object, it's constructor calls Base's constructor automatically. In Base's constructor, the type of the object is "Base". So when you call method() in its constructor, all it knows about is itself. So it calls its own method(). Only after you get to Derived's constructor will the object's type be "Derived". Same thing with destruction. Derived's destructor runs first, destroying itself. It then goes up to Base's destructor. Again at this point Base only knows about itself and calls its own method().

It's different in C#. An object can only ever have one (fixed) type. It doesn't morph like in C++. So when you call Derived's constructor, it calls Base's constructor automatically. But at this point the type of the object has already been set to "Derived" and the system knows that it has overriden method(). So in Base's constructor, method() drops down to Derived's implementation. Same with destruction.

Apparently, the difference in C# has to do with the fact that it has a GC. The GC always needs to know the exact size of the object... it can't change. I dunno if this is the case in Java as well. I would assume so.

Personally, I feel the C++ way makes more sense. It's more logical. What dyou think?

No comments: