Jump to content


Calling most specific virtual function


13 replies to this topic

#1 Nick

    Senior Member

  • Members
  • PipPipPipPip
  • 1225 posts

Posted 29 September 2005 - 08:29 AM

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

#2 .oisyn

    DevMaster Staff

  • Moderators
  • 1810 posts

Posted 29 September 2005 - 09:04 AM

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.
C++ addict
-
Currently working on: the 3D engine for Tomb Raider.

#3 bramz

    Valued Member

  • Members
  • PipPipPip
  • 189 posts

Posted 29 September 2005 - 07:11 PM

.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
hi, i'm a signature viruz, plz set me as your signature and help me spread :)
Bramz' warehouse | LiAR isn't a raytracer

#4 .oisyn

    DevMaster Staff

  • Moderators
  • 1810 posts

Posted 30 September 2005 - 07:27 AM

[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

C++ addict
-
Currently working on: the 3D engine for Tomb Raider.

#5 Nick

    Senior Member

  • Members
  • PipPipPipPip
  • 1225 posts

Posted 02 October 2005 - 08:53 AM

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!

#6 corey

    Member

  • Members
  • PipPip
  • 78 posts

Posted 07 October 2005 - 09:32 PM

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

#7 Jesse M

    Member

  • Members
  • PipPip
  • 32 posts

Posted 08 October 2005 - 03:07 AM

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();
}

FRAG THE PLANET
Ed Helms: Alcohol causes problems and guns solve problems. I don't see why you can't have guns in bars.
Other guy: That's a stupid idea.
Ed Helms: Yeah, if your a pussy.

#8 corey

    Member

  • Members
  • PipPip
  • 78 posts

Posted 08 October 2005 - 03:45 AM

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

#9 moe

    Valued Member

  • Members
  • PipPipPip
  • 270 posts

Posted 08 October 2005 - 04:43 AM

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…

#10 corey

    Member

  • Members
  • PipPip
  • 78 posts

Posted 08 October 2005 - 05:26 AM

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

#11 moe

    Valued Member

  • Members
  • PipPipPip
  • 270 posts

Posted 08 October 2005 - 08:04 AM

Thx for clarification.

#12 corey

    Member

  • Members
  • PipPip
  • 78 posts

Posted 08 October 2005 - 04:40 PM

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

#13 .oisyn

    DevMaster Staff

  • Moderators
  • 1810 posts

Posted 09 October 2005 - 05:09 PM

moe said:

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.
C++ addict
-
Currently working on: the 3D engine for Tomb Raider.

#14 moe

    Valued Member

  • Members
  • PipPipPip
  • 270 posts

Posted 09 October 2005 - 09:26 PM

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





1 user(s) are reading this topic

0 members, 1 guests, 0 anonymous users