March 28, 2024, 02:49:10 PM

News:

IonicWind Snippit Manager 2.xx Released!  Install it on a memory stick and take it with you!  With or without IWBasic!


OOP Tutorial 4

Started by Parker, October 12, 2007, 12:15:10 AM

Previous topic - Next topic

0 Members and 1 Guest are viewing this topic.

Parker

October 12, 2007, 12:15:10 AM Last Edit: October 12, 2007, 06:56:43 PM by Parker
This is long overdue...

In this tutorial I'm going to explain virtual functions and overriding. Previously I was working with the CPoint class. A point isn't a very complicated object, so it worked for the first three tutorials, but I don't really see anywhere that I can add virtual functions to a point class. So I'm going to start some new classes. This time I'm going to use animals to demonstrate. Animals all have a lot in common, but some characteristics ("speaking" for example) are different. This makes animals a perfect example for virtual functions.




Before I go any further, I'd like to spend a little time discussing capitalization and naming classes and variables. Every (well, every good) programmer has his/her own rules of naming elements of the program. For example, variables start with lower case, names use CamelCase instead of underscores_to_break_up_words, etc. There are a few common rules for naming classes also.
- Usually, class names start with an upper case letter, and are typed in CamelCase. Ex: class MyNetworkServer
- Sometimes people prefix class names with a 'C' (which stands for "Class"). This originates (as far as I know) from MFC, where everything is CWnd, CBrush, etc. The MFC wizards in Microsoft IDEs create classes with this prefix.
- A lot of the time libraries will prefix class names with an abbreviation of that library's name. Wx is the best example of this I can think of: wxWindow, wxPoint, wxApp, etc. This is to avoid conflicts with your variables and classes (though some people decide to use this prefix for their own programs that use that library, e.g. MFC). The exception is when namespaces (C++, .NET) or packages (Java) are used, but this topic doesn't apply to Aurora or EBasic because those aren't supported.

I used the name CPoint previously, because it is a well-accepted naming convention, and because we already had a type called POINT. However, I don't usually use the C prefix. In this tutorial, we will deal with the Animal, Dog, and Cat classes.

I'm going to begin by defining the Animal class. I think we can all agree that if we didn't know what an animal was, we'd have a hard time imagining a dog or cat.
class Animal
private
'' How old am I?
int age
public
declare Animal( )
'' I don't really need a destructor at this point
'' but it's good practice so I'll include it anyway.
declare _Animal( )

'' Since I provided a variable, age, above, we should probably do something with it.
declare GetOlder( ) '' Makes me (the Animal object) grow one year.
declare GetAge( ),int '' What if we need to know my age?

'' Here is something animals do differently,
'' so I am going to use the virtual keyword.
declare virtual Speak( )
end class


All this code up until Speak( ) should look familiar. When you reach Speak( ), however, you'll notice I used the virtual keyword. To the compiler, this means the function will be stored in an array of pointers, called the v-table, and called using that array, but you don't have to think about it that way. All you really need to know about the virtual keyword is that it allows you to change the behavior of that sub for any derived classes (classes that extend Animal). This means, if an animal makes the generic animal noise (if there is one), you can actually provide a new Speak( ) method in your Dog or Cat class, and it will make the correct "Ruff!" or "Meow!" noise (or whatever noise dogs and cats make in your country ;)).

To demonstrate this concept, I'm going to begin by defining all the methods for the Animal class:

sub Animal::Animal( )
'' When we are born, we are 0 years old.
age = 0
end sub

sub Animal::_Animal( )
'' No cleanup to do in this class.
end sub

sub Animal::GetOlder( )
'' I need to age one year
age++
end sub

sub Animal::GetAge( )
return age
end sub

'' Notice I'm treating this just like a normal sub here.
sub Animal::Speak( )
print "(generic animal noise)"
end sub


Now we are going to see how virtual functions work with the Dog class. First I will define the class:
class Dog, Animal
'' No private members, and the default access is public
'' But it's smart to always specify it.
public
'' I don't need either of these really, but again, good practice...
declare Dog( )
declare _Dog( )

'' Since I extend Animal, I already have GetOlder( ) and GetAge( )

'' I also have Speak( ), but I don't like that one.
'' Dogs make a distinct noise, and I would like to know that.
'' So I'm going to redefine Speak( ).
declare virtual Speak( )
end class


Now, just like I usually do, I'm going to give a body to all these methods.
sub Dog::Dog( )

end sub

sub Dog::_Dog( )

end sub

'' Again, I treat this like a normal sub.
sub Dog::Speak( )
print "Ruff!"
end sub


If you're following me, you'll notice that if Dog inherits all of Animal's methods, something doesn't look right. Dog has two Speak( ) methods, so how does the compiler know which one to use? The answer is that usually it doesn't, but because we used the virtual keyword, it does. The virtual feature is there because sometimes we don't know which inherited type we will have. For example, say we have a Person class. A Person can own an Animal, but we don't know ahead of time whether they will have a Dog, Cat, Iguana, BlueWhale (kidding...), etc. However, we want their Animal to make the right noise so they don't have to make constant trips to the vet. If we did this:

*<Animal>pet.Speak( )

and we didn't use the virtual keyword, the compiler would assume that it was just a plain old Animal, and we would get "(generic animal noise)". Not very reassuring. However, if we define the Speak( ) method as virtual, suddenly we get a "Ruff!" or "Meow!" or "(Low rumble)" in the case of the whale.

I'm also going to define the Cat class here, and then we can see how it works.
class Cat, Animal
public
declare Cat( )
declare _Cat( )

declare virtual Speak( )
end class

sub Cat::Cat( )

end sub

sub Cat::_Cat( )

end sub

sub Cat::Speak( )
print "Meow!"
end sub


Now we can use these classes to see this concept in action.
pointer myDog
pointer myCat

myDog = new( Dog, 1 )
myCat = new( Cat, 1 )

'' Now, without telling the compiler what type myDog and myCat are,
'' (don't worry, it doesn't know), we're going to make them Speak.

*<Animal>myDog.Speak( )
*<Animal>myCat.Speak( )

delete myDog
delete myCat

do:until inkey$<>""


If you're not convinced the compiler didn't know what type they were, take off the virtual keyword and you'll see, they both make generic animal noises ;D.

At this point, I just want to make one final note of clarification. I didn't really say a lot about overriding, but overriding is really just the practice of changing a subroutine to do something else using the virtual feature. Animal's Speak( ) method is overridden in Dog and Cat.

I hope that answers any questions on the subject, but if it doesn't I'd be glad to clarify.

To be honest, I don't think there's much more in EB or Aurora in terms of OOP that hasn't been covered in these tutorials, but please let me know if there's a concept that I've missed. Otherwise, I'm thinking about one final tutorial about advanced C++ features and how to convert them.

Edit - missed some delete statements... :-[

Haim

Great tutorial.
I am eagerly awaiting the next part
Thanks,

Haim

pistol350

That's Fantastic !
Please keep it up Parker.  8)
I'd never really understood the usage of the Virtual keyword.
Even after reading my "Teach Yourself c++ in 21 days" book (BTW that title makes me laugh  ;D ),
i never managed to catch up what was said about Virtual keyword, i guess i should throw my book away  :)
I realy like the way you explain things.

Cheers!
Peter
Regards,

Peter B.

Parker

Thanks everyone.

I thought I should note this: while writing this tutorial I realized that calling delete in EBasic does not call the destructor. This requires some way to tell DELETE what type the pointer is. I've recommended typed pointers to Paul, since that's really the only elegant way of dealing with this (and it would be a much appreciated addition to the language).

Ionic Wind Support Team

Parker,
Yes it does.  You just have to use the SETTYPE command


Class Parker_Lesson
declare _Parker_Lesson()
Endclass

sub Parker_Lesson::_Parker_Lesson()
print "Destructor called"
endsub

openconsole

pointer p:p = new(Parker_Lesson,1)
settype p,Parker_Lesson
delete p

do:until inkey$ <> ""
closeconsole
end
Ionic Wind Support Team

Parker


Ionic Wind Support Team

Yep.  And after the SETTYPE you no longer need to typecast the object either.

Ionic Wind Support Team

LarryMc

Wasn't there something about type casting not carrying through into subs? or other source filesin a project?

If so, how is this different?

Larry
LarryMc
Larry McCaughn :)
Author of IWB+, Custom Button Designer library, Custom Chart Designer library, Snippet Manager, IWGrid control library, LM_Image control library

Ionic Wind Support Team

You can use SETTYPE on any pointer.  Even those passed to subroutines.
Ionic Wind Support Team