Calling most specific virtual function

99f6aeec9715bb034bba93ba2a7eb360
0
Nick 102 Sep 29, 2005 at 08:29

Hi all,

I’m probably just loosing it, but could anyone explain to me why the C++ code below prints out “1” instead of “2”, and how to change that:

#include <stdio.h>

class Base
{
public:
    virtual void foo() = 0;
};

class Derived1 : public Base
{
public:
    Derived1()
    {
        foo();
    }

    void foo()
    {
        printf("1");
    }
};

class Derived2 : public Derived1
{
public:
    void foo()
    {
        printf("2");
    }
};

int main()
{
    Base *x = new Derived2();
}

As far as I know the vtable of Derived2 should point all foo calls to Derived2::foo. But what appears to happen is that the Derived2 constructor calls the Derived1 constructor, which in turn calls the Derived1 implemenation of foo. It probably has something to do with vtables not being initialized until the constructor has finished, but then I don’t know how to get the behaviour I’m looking for. Should I just call foo after the construction has completely finished? That’s rather ugly…

Thanks,

Nick

13 Replies

Please log in or register to post a reply.

340bf64ac6abda6e40f7e860279823cb
0
_oisyn 101 Sep 29, 2005 at 09:04

Ah, classic mistake :).

You need to realize that the sole purpose of a constructor is to initialize the object. The object is of a particular type only after the constructor has run. The constructor sets up the vtable, so in Derived1:: Derived1, you’re only a Derived1, even if you’ve actually created a Derived2. So calling foo in this regard will just call Derived1::foo as the object is still a Derived1. Calling foo() from Base::Base will even result in a compile error due to the fact that Base::foo is pure and has no implementation.

You might think this is annoying but it’s actually a good thing. Possible members of Derived2 are not constructed yet, so calling Derived2::foo before Derived2:: Derived2 has run wouldn’t do you much good since all the members are in an undefined state.

Work-around: move the logic to an init() function of some sort, and call foo from there. The downside is that the code that created the object should call this function, as calling it from the Derived2 constructor would disable any initialization for more derived classes.

25bbd22b0b17f557748f601922880554
0
bramz 101 Sep 29, 2005 at 19:11

.oisyn is correct. The same is true for destructors, only the other way around. Some people try to be clever by calling a regular function that calls the virtual function instead, but that obviously doesn’t help :)

Thus in short: Never call a virtual function from a structor.

Bramz

340bf64ac6abda6e40f7e860279823cb
0
_oisyn 101 Sep 30, 2005 at 07:27

[theoretical rambling]

In languages such as Java or those of .Net this is in fact possible, although it doesn’t make any sense. The constructor hasn’t run yet, so any members are only initialized to their default values.

C++’s approach is much more logical, although it can be a pain in the ass. Perhaps I’d like to see a feature for post-constructors and pre-destructors of some sort… Special structors that are called respectively after the constructor has run and before the destructor is going to be run, with the same fall-through mechanism as the structors themselves have. These special structors should be called by the code that creates or destroys the object of course, just like the normal structors. In this way, you can call foo from Derived1’s post-constructor or pre-destructor, so Derived2::foo get’s called since it is either after construction (so the object is already a Derived2) or before the destructor (likewise; the object is still a Derived2).

But come to think of it, it can actually be simulated using templates:

#include <stdio.h>

template<class T> class object : public T
{
public:
    typedef T super;

    object() : super()
    {
        super::postconstructor();
    }

    object(const object & other) : super(other)
    {
        super::postconstructor();
    }

    template<class P1> object(P1 p1) : super(p1)
    {
        super::postconstructor();
    }
    //.
    //.
    //template<class P1, ..., class PN> object(P1 p1, ..., PN pn) : super(p1, ..., pn)
    //{
    //  obj.postconstructor();
    //}

    ~object()
    {
        super::predestructor();
    }
};

class Base
{
public:
    virtual ~Base() { }
    virtual void foo() = 0;
};

class Derived1 : public Base
{
public:
    Derived1()
    {
        foo();  // this just calls Derived1::foo
    }

    ~Derived1()
    {
        foo();  // likewise
    }

    void foo()
    {
        printf("1\n");
    }

protected:
    void postconstructor()
    {
        foo();  // calls any further derived version of foo()
    }

    void predestructor()
    {
        foo();  // likewise
    }
};

class Derived2 : public Derived1
{
public:
    void foo()
    {
        printf("2\n");
    }

protected:
    void postconstructor()
    {
        Derived1::postconstructor();
        // do any more logic here
    }

    void predestructor()
    {
        // do any more logic here
        Derived1::predestructor();
    }
};

int main()
{
    Base * x = new object<Derived2>();
    delete x;
}

Outputs:

1
2
2
1
99f6aeec9715bb034bba93ba2a7eb360
0
Nick 102 Oct 02, 2005 at 08:53

Thanks all!

I now understand it’s just a matter of language consistency and not a technical problem of filling the vtable. So my best option is to leave the constructor ‘empty’ and do the actual initialization with a virtual call after the construction.

I remember stumbling over a similar situation a while ago. Someone claimed that after the constructor an object should be completely ready for use, and ironically I proved him wrong with an example very much like this situation. :D

Anyway, thanks for clarifying it like this. And thanks .oisyn for the interesting workaround!

F6ad85487ec271d99a0ef7a4e1b426d6
0
corey 101 Oct 07, 2005 at 21:32

I thought that I’d mention two areas in the C++ specification that deal with this:

Section 12.6.2 paragraph 8
Section 12.7 paragraph 3

I personally think the example in paragraph 3 is a little odd but the text spells it out plainly.

corey

F33eec98e0fc761467c315aad881c035
0
Jesse_M 101 Oct 08, 2005 at 03:07

I’ve had this problem before too and it has a really simple solution. I’m not sure if this is ISO C++ but it works in GCC. I don’t know why anyone has not addressed this prior to me. It’s pretty clear.

#include <stdio.h>

class Base
{
public:
    virtual void foo() = 0;
};

class Derived1 : public Base
{
public:
    Derived1()
    {
        this->foo();
    }
   /**** This needs to be virtual too! ****/
    virtual void foo()
    {
        printf("1");
    }
};

class Derived2 : public Derived1
{
public:
   /**** Call Ctor ****/
    Derived2() : Derived1
    {
    }
   /**** So does this! ****/
    virtual void foo()
    {
        printf("2");
    }
};

int main()
{
    Base *x = new Derived2();
}
F6ad85487ec271d99a0ef7a4e1b426d6
0
corey 101 Oct 08, 2005 at 03:45

Jesse,

Check out Section 10.3 paragraph 2 of the specification then:

If a virtual member function vf is declared in a class Base and in a class Derived, derived directly or indirectly from Base, a member function vf with the same name and same parameter list as Base::vf is declared, then erived::vf is also virtual (whether or not it is so declared) and it overrides97) Base::vf. For convenience we say that any virtual function overrides itself. …

I’m just posting information, I don’t know all the compilers involved.

corey

87e614b8b888bb2c4485c1ac16d8c779
0
moe 101 Oct 08, 2005 at 04:43

Just out of curiosity. Is it wrong to think of it like the following?

When you don’t provide a constructor yourself then a default constructor is created. In this case the default constructor will call the constructor from its super class. Since the super class is Derived1 it will call

printf(“1”);

as it is defined in the Derived1 class. The default constructor will then look something like this:

class Derived2 : public Derived1
{
public:
    Derived2()
    {
        Derived1::Derived1();
    }
    ...
};

But you want it to call foo() from Derived2 therefore all you have to do is add a constructor yourself like:

class Derived2 : public Derived1
{
public:
    Derived2()
    { 
        foo();
    }
    ...
};

And not call Derived1:: Derived1(); in your constructor.

Hope I didn’t bring in more confusion…

F6ad85487ec271d99a0ef7a4e1b426d6
0
corey 101 Oct 08, 2005 at 05:26

The way I understand the specification (not withstanding implementations of different compilers) is that the initial results are correct.

However, the reason is not an incomplete vtable but rather scoping rules as defined in the sections I posted previously.

Any method declared with the same parameters as a virtual method in a base class automatically becomes virtual and overrides the base class properly.

A method or virtual method must be called from a non-base class initializer or variable initializer in a constructor’s mem-initializer list to have expected behavior. Base classes are always initialized by the time the constructor body is executed so that will always have expected behavior.

The expected behavior is for the scope of the current class being initialized or its base classes to provide the proper final override of a method virtual or not. This is a simple case and more complex situations can come up and are provided for. A derived class’ override will never be called from a base classes initialization.

corey

87e614b8b888bb2c4485c1ac16d8c779
0
moe 101 Oct 08, 2005 at 08:04

Thx for clarification.

F6ad85487ec271d99a0ef7a4e1b426d6
0
corey 101 Oct 08, 2005 at 16:40

I don’t really use virtuals that way, so I’m interested in how g++ 4 handles this. If it acts differently than I think, might be an good case to post to the gcc list to see what they think.

corey

340bf64ac6abda6e40f7e860279823cb
0
_oisyn 101 Oct 09, 2005 at 17:09

@moe

Just out of curiosity. Is it wrong to think of it like the following? When you don’t provide a constructor yourself then a default constructor is created. In this case the default constructor will call the constructor from its super class.

Not quite: the base constructor is _always_ called, whether you specify a constructor in a derived class or not. You can explicitely call a base constructor in the initializer list:

Derived2::Derived2() : Derived1()
{
    // some stuff
}

But if you don’t specify what base constructor to call, the default constructor is called. If there isn’t a default constructor, you’ll get a compile error.

Not calling the base constructor makes no sense: that means the base isn’t initialized and so your instance is pretty useless. And calling foo() from Derived2 isn’t that good either, if you ever need to derive a Derived3 from Derived2, Derived2::foo() get’s called instead of Derived3::foo and there’s no way to circumvent this.

87e614b8b888bb2c4485c1ac16d8c779
0
moe 101 Oct 09, 2005 at 21:26

Good point. I did not even think one might derive again from Derived2. Thx for the details.