🎉 Celebrating 25 Years of GameDev.net! 🎉

Not many can claim 25 years on the Internet! Join us in celebrating this milestone. Learn more about our history, and thank you for being a part of our community!

C++ Workshop - Inheritance (Ch. 12)

Started by
39 comments, last by Dbproguy 16 years, 1 month ago
Demos,

Ceoddyn has the right idea. When you declare a function as virtual, it gets added to the class's virtual function table. This little table creates a level of indirection allowing you to assign a call to that function to any function by that name, regardless of where it falls in the inheritance chain, simply by changing which interface you're addressing. The best way to explain perhaps is to use an example.
class Item{public:    virtual UseItem()    {        cout << "You eat/drink the item." << endl;    }};class Weapon : public Item{public:    virtual UseItem()    {        cout << "You wield the weapon." << endl;    }};Now, if I create an object of type "Weapon" such as the line below and call UseItem it calls the function as normal.// This does exactly what you expectWeapon* pWeapon = new Weapon();pWeapon->UseItem();Where it's different, is if I've got a pointer which claims to be a different interface than the object actually is..for exampleItem* pItem = pWeapon; // assign the weapon to the item pointer, since a weapon IS-A item this is perfectly legal.// Here's the magic of virtual functions.  When I call UseItem on this pointer// i see "You wield the weapon."  because pItem ACTUALLY points to a weapon,// and since the function is virtual, the correct call is resolved.// if the function UseItem had not been virtual, it would not have been able// to resolve the call to the correct type, and I would have seen // "You eat/drink the item." instead.pItem->UseItem(); 

Give it a try, you'll see for yourself.

Cheers!
Jeromy Walsh
Sr. Tools & Engine Programmer | Software Engineer
Microsoft Windows Phone Team
Chronicles of Elyria (An In-development MMORPG)
GameDevelopedia.com - Blog & Tutorials
GDNet Mentoring: XNA Workshop | C# Workshop | C++ Workshop
"The question is not how far, the question is do you possess the constitution, the depth of faith, to go as far as is needed?" - Il Duche, Boondock Saints
Advertisement
AH!!!!

i see. *light bulb just lit up*
that makes sense now. Thanks!
[OPINION]

Quote: Original post by Deyja
I have never seen a class that is both nested in and derived from the same class.


Well, not practical in all senses, but I have used something like this:
struct Array { virtual int Size(); ... struct Static; struct Dynamic;}; //You can declare these inside the class, but I preferred to declare 'em outstruct Array::Static : public Array { ... }struct Array::Dynamic : public Array { ... }

You could have the class "StaticArray" instead of "Array::Static" and "DynamicArray" instead "Array::Dynamic", but I simply preferred to do it like this to keep it straight in my head, and potentially reduce typing (if there is ever a 'using scope' feature in C++).

NOTE: I'm using these constructs in a parsing environment (script writing, console management, etc) and they won't be used in the code as a language parallel. (Woohoo! I made up a term...)

Just keep in mind that these cases are particularly rare, and shouldn't be used in such a fashion that they force a massive amount of code into one class declaration.
Example: "class Item { class Bioform : public Item { class Complex : public Bioform { class Animal : public Bioform { class Canine : public Animal { class Domesticus : public Canine { class Mutt : public Shepherd, public Baboon, public Boa, public Fish { ... } } } } } } }" - And yes, I know I skipped a couple of logical steps in classification, and inserted a few illogical ones).
By the end of that, to refer to your puppy, you have to go through this: Item::Bioform::Complex::Animal::Canine::Domesticus::Mutt. Fun, no?
That, right there, was a set of some of the most horrible abuses of OOP language constructs. [/concede]

Once again (and again, and again, and again...), it's all based on personal preference. What looks as does a pure pond of crystal clear spring water to one, to another looks like a festering puddle of brown-colored stagnant rain water with its own unique ecosystem of mutant mosquitoes (and a heavy amount of redundant modifiers). [Where I just went with that nobody knows. I hate English class.]
[/OPINION]

[Edited by - deadimp on November 11, 2006 7:35:46 PM]
Projects:> Thacmus - CMS (PHP 5, MySQL)Paused:> dgi> MegaMan X Crossfire
Well, finished another chapter, did once again a search to find the Quizzes - Exercises thread, nothing.

Chapter 12.

1) What is derivation?
A) Derivation is a way of expressing the is-a relationship. You derive a new class, Dog, from the class Mammal, any method or data Mammal has, that is not private, will be inherited by Dog.

2) What happens if you try and derive from a class which has not yet been declared?
A) You get an error stating that the base class is undefined.

3) What is the purpose of declaring member variables as protected?
A) The purpose of this is to allow derived classes to use the data types and methods of the base class.

4) When instantiating an object which is derived from one or more base classes, which constructors are called, in what order?
A) First the base class's constructor is called, then the constructor of the first inherited class of the base class, then the constructor of the second class which inherited from the first class untill we get up ontoo the actual constructor for which the object is created for.

5) What about destructors? What order are they called in?
A) They are called in the reverse order as the constructors are called.

6) What is it called when a derived class replaces the functionality of a base class with its own implementation?
A) It is called "Overriding".

7) If you've overridden a method in a derived class, is it still possible to access the base class version? If so, what's the syntax?
A) Yes, it is still possible to call it by fully qualifying the name of the method. You do this by writing the base name, followed by two colons and then the base method name: baseClass::Method()

8) What does declaring a method as virtual do?
A) It allows pointers to base classes to be assigned to derived class objects.

9) Can virtual functions be used on non-pointer, non-reference variables?
A) No, doing this will slice the objects v-pointer intoo calling only the base class it's constructor, not it's derived constructor(s).

10) When should you declare the destructor of your class as virtual? What happens when you delete a base pointer to a derived class with a non-virtual destructor?
A) You should declare them virtual from the moment that any of the functions in your class are virtual. When you don't use a virtual destructor in combination with an object that is created with the use of a virtual method, the wrong destructor is called and the pointer isn't deleted. You actually create a memory leak.

11) Can constructors be virtual? Can copy constructors be virtual?
A) No, constructors can't be virtual, copy constructors can be made virtual threw the use of a base class Clone() method that calls the copy constructor explicitly.
Quote:
8) What does declaring a method as virtual do?
A) It allows pointers to base classes to be assigned to derived class objects.


Not quite. Deriving from a class allows this. A virtual method means that the function that will be called is decided at run time rather than compile time, based on the object passed to the function:
class Base { public: virtual void foo(){} };class Example1 : public Base { virtual void foo(){} };class Example2 : public Base { virtual void foo(){} };class Example3 : public Example2 { /* doesnt reimplement foo */ };void bar( Base &b ) {   b.foo();}int main() {    Base b;    Example1 one;    Example2 two;     Example3 three;    bar(b);     // calls Base::foo    bar(one);   // calls Example1::foo    bar(two);   // calls Example2::foo    bar(three); // calls Example2::foo, Example 3 doesn't reimplement foo}


Quote:
9) Can virtual functions be used on non-pointer, non-reference variables?
A) No, doing this will slice the objects v-pointer intoo calling only the base class it's constructor, not it's derived constructor(s).


Not quite. Virtual functions work fine on non-pointer/reference variables. What you describe occurs when you assign a derived object to an object of its base class:
class Base { };class Derived : public Base { };void foo() {   Derived d;   Base b = d; // oh no, slicing!}


This can also occur if you accidentally pass an object of a derived class to a function taking a base class by value.
Quote: Original post by rip-off
Not quite. Virtual functions work fine on non-pointer/reference variables. What you describe occurs when you assign a derived object to an object of its base class:


Hi rip-off,

Thanks for the explanation, though I understand what you ment with Question 8 and saw that I was not correct there, I'm confused with your reply towards Question 9.

In the book it clearly is stated and I quote:
Quote:
Page 404: Functions in the base class can be overridden in the derived class. If the base class functions are virtual, and if the object is accessed by pointer or reference, the derived class's functions will be invoked, based on the runtime type of the object pointed to.


That does mean you can't use objects by value to get the same effect right? Doing this will invoke the base constructor and not the derived constructor as you actually desire? So, you are slicing, atleast, that's how I see it.

Quote: Original post by J0Be
Hi rip-off


Hi.

Quote:

In the book it clearly is stated and I quote:
Quote:
Page 404: Functions in the base class can be overridden in the derived class. If the base class functions are virtual, and if the object is accessed by pointer or reference, the derived class's functions will be invoked, based on the runtime type of the object pointed to.


That does mean you can't use objects by value to get the same effect right? Doing this will invoke the base constructor and not the derived constructor as you actually desire? So, you are slicing, atleast, that's how I see it.


If you have an object by value, then the compiler knows the type of the object at compile time. It doesn't need to check which function to call at runtime, it can make the decision at compile time. It can (and will) call the right function directly. There is no slicing, slicing can only occur when you assign 2 variables with different types.

I'm not sure why you are bringing up constructors, remember we already have an object and we are going to call on of it's functions.

Unless of course you are talking about the accidental slicing of an object passed as a parameter( as I alluded to at the end of my last post):
class Base {   public:      virtual void foo(){}};class Derived : public Base {   public:      virtual void foo(){}};void bar( Base base ) {    base.foo();}int main() {    Base base;    Derived derived;    derived.foo(); // no slicing, compiler calls Derived::foo    bar(base);    bar(derived); // slice occurs here!                  // also compiler will call Base::foo on the sliced object in bar...}


Remember the question though, which was "will virtual functions work on value objects". The answer is that functions declared virtual can be called on a value object and it will work, however the decision as to which function will be run is made at compile time, not runtime.
Good morning rip-off.

Quote: Original post by rip-off

Unless of course you are talking about the accidental slicing of an object passed as a parameter( as I alluded to at the end of my last post)


Yep, I was referring to that.

Quote: Remember the question though, which was "will virtual functions work on value objects". The answer is that functions declared virtual can be called on a value object and it will work, however the decision as to which function will be run is made at compile time, not runtime.


Think that was my problem, I missinterpreted the question.


Was wondering if you could help me out with the following aswell, there are a few exercises in the book and I worked out the first and second without a problem, but, I'm stuck at number three, which is related to Exercise two, they are the following:
Quote:
2) Show the declaration of a class Square, which derives from Rectangle, which in turn derives from Shape.
3) If, in Exercise 2, Shape takes no parameters, Rectangle takes two (length and width), but Square takes only one (length), show the constructor initialization for Square.


I have the following solution for two.
#include <iostream>class Shape{public:	Shape() {}	 ~Shape() {}};class Rectangle : public Shape{public:	Rectangle() {}	 ~Rectangle() {}};class Square : public Rectangle{public:	Square() {}	 ~Square() {}};int main(){	return 0;}


Building on this, I made Exercise three into this:
#include <iostream>class Shape{public:	Shape() {}	 ~Shape() {}};class Rectangle : public Shape{public:	Rectangle() {}	Rectangle(int length, int width);	 ~Rectangle() {}protected:	int m_Length;	int m_Width;};Rectangle::Rectangle(int length, int width) : m_Length(length), m_Width(width) {}class Square : public Rectangle{public:	Square() {}	Square(int length);	 ~Square() {}};Square::Square(int length) : Rectangle(length, width) {}int main(){	return 0;}


Problem is, I keep getting an error saying that width is undeclared when using it during Square's initialization? How come? The overloaded constructor of Rectangle is known, why is it then, when using it with Square, it's not known? Is that related to scope?
Possible descent solution?

Square::Square(int length) : Rectangle(length, m_Width = 1) {}
Bearing in mind that a Square isn't really a Rectangle, the best solution in regard to your exercise is to set both the Rectangles "width" and "length" to the value passed in the Square's constructor.

Mathematically (although as you have seen if you read the above link, not conceptually) a square is a rectangle where the width and height is the same.

I would use:
class Square : public Rectangle{public:	Square() {}	Square(int length);	 ~Square() {}};Square::Square(int length) : Rectangle(length, length) {}

This topic is closed to new replies.

Advertisement