Archived

This topic is now archived and is closed to further replies.

kuphryn

Static Binding & Dynamic Binding :: C++

Recommended Posts

kuphryn    210
Hi. Is there an advantage to using dynamic binding instead of static binding? I have both Deitel & Deitel C++ How to Program and Stroustrup''s Special Edition. I read all of the first book and finished chapter fifteen of Stroustrup. So I understand virtual functions and virtual base. However, I am not to the point where I can implement virtual functions and virtual base without asking: How do you implement them effectively? For example, "virtual" only works for pointer and reference. That is completely conceivable. However, it seems you have to declare an object of a derived class and then *cast* a new class from the base class to the derive class just to get it working right. ----------------- classDerived cDerived = new classDerived; classBase *cBase = &cDerived; ----------------- The example above comes straight from Deitel & Deitel. So everything looks okay. What is the point of declaring two separate classes? I understand that the use of "virtual," but I do not understand the performance of "virtual" if you have to declare an object of the derived class and an object of the base class. Under what specific situation do you implement dynamic binding? Please be very specific because I found that neither Deitel & Deitel nor Stroustrup give a convincing example of dynamic binding. Lastly, I too want to implement dynimic binding if it is possible to declare *one* object of either the base class or the derived class. The following is *not correct*: ------------------------ classBase *cDynamic = new classDerived ------------------------ In other words, *virtual* would be great if there is a way to use it without having to declare two seperate classes like the example from Deitel & Deitel. Thanks, Kuphryn

Share this post


Link to post
Share on other sites
Xai    1838
first of all .. the second example you posted WOULD be correct except that you probably do not have a virtual destructor ... so when it comes time to delete the object .. it erroneously calls the base classes destructor ...

So ... here''s the deal ... you make a base class ... with virtual functions .. when you realize there might be more than one implementation which meets all the requirements of behavior that your base class imposes, but have other usefull differences ...

The standard "shape" example is very good. Or an example from my actual program is "ScreenObject" ... my game was a 2D video slot machine ... and the "ScreenObject" base class defined function to get and set the on screen position, get and set a zvalue (so it was really a LAYERED 2D program), draw the object, and add and release references to the object.

Then I had differnt derived classes, 1 that was specialized for a statically positioned simple image, 1 that managed a series of animating images based on frames elapsing, 1 that moved itself across the screen (or through Z layers) based on a math functor you provided (a function object which gave it variable values on each sucessive call).

So, using this system I was able to write a "ScreenManger" class that maintained lists of ScreenObject''s that were used in each ''mode'' ... so during initialization you add all the objects to the manager (after construction), and then during operation you tell the manager which mode to switch to, and adds a ref to every object in the new mode, releases a ref to every object in the old mode, and off you go ... the whole screen has changed, and objects had internal memory buffers allocated or deleted, all without the screen manager having any idea of the internal details.

That is the benifit of base classes.

Here''s another example.

I have a base class like this:

  
class TimedObject
{
public:
virtual void Tick(void) = 0;
};


and that''s it .. nothing but a pure virtual void function called Tick. But what this means is that I can had variious timers in my game, each with a list of objects that it should notify when the time elapses (and one timer is based on REAL time, another on game FRAMES, so I can set things whichever way makes sense). Then when its timer fires, it traverses a list of pointers to timed objects ... calling tick on each on ... therefore the timer can be based on anything in the world (even pressing a key to single step during debuging) ... and the actions taken can be anything in the world (as implemented by the derived class) ... so you get a REALLY powerfull system of tying actions to events, with such a simple little base class.

NOTE: there are many improvements on the TimedObject idea that add a message parameter ... so the derived class can be plugged into multiple timers, and do different things for each ... but these systems vary depending on your needs.

Hope This Helped.

Share this post


Link to post
Share on other sites
null_pointer    289
Xai: I do not think that you understand what he is saying.


quote:
Original post by kuphryn

However, it seems you have to declare an object of a derived class and then *cast* a new class from the base class to the derive class just to get it working right.


Casting to a pointer does not introduce a new object. Pointers simply hold the addresses of other objects. That's why they are called pointers: they only "point" to other objects.

Example:

base* p = new derived; 


Look closely at the symbols used in that declaration. When the asterisk is used between a type and a name like that, you are declaring a pointer to a base object, not an actual second object. "p" is just a tiny little object does absolutely nothing but hold the address of the new derived object that you created.

Basically, a pointer is just an alias.

Edited by - null_pointer on December 31, 2001 2:13:19 PM

Share this post


Link to post
Share on other sites
Guest Anonymous Poster   
Guest Anonymous Poster
the problem is that you have no idea what virtual functions are for. Forget whatever you think they are for completely, you are very wrong. The whole point entirely is that so you can make multiple classes. Casting is not involved either, not one bit. Any example that shows casting is using it incorrectly.

The point of using the virtual keyword is to make inheritance work. That is all it does. Why would you want to use inheritance? Well you might have a bunch of monsters in your game. Each monster will need an AI. Every now and then you can call your Think() method on the monster''s AI. If every monster has the same kind of AI that would be boring, so what you do is you make a base AI class with a virtual Think(). Then in each subclass you write a new think method. This means that each monster will have a different fighting style. All you need to do is change which type of AI the pointer points to.

PS: the Deital & Deital book is bad, they leave the good stuff to chapters 19 and 20.

Share this post


Link to post
Share on other sites
kuphryn    210
Okay. Thanks guys.

Here is an example:

Base Class : Movies

Derived Class #1: Action
Derived Class #2: Comedy

null_pointer: Can you post a specific example that would mirror what I said above using dynamic_casting if applicable?

------------------------
Static binding:

Action *aClass = new Action;

cout << aClass->getTitle(); // virtual function

Anonymous Poster:
------------------------

How would you implement the example above such that the code uses dynamic binding?

Thanks,
Kuphryn



Edited by - kuphryn on December 31, 2001 2:41:35 PM

Share this post


Link to post
Share on other sites
kuphryn    210
null_pointer:

I see your point.

// Using class Movie exampe from above

----------------------
Dynamic binding

Action *aClass = new Action;

Movies *aMovies = &aClass;

cout << aMovies->getTitle(); // virtual function

// Both Action and Comedy have their own function: getTitle().
----------------------

null_pointer:

Are you implying that there is little resource loss or performance reduction even if you declare two pointers, one to the base and the second to one of the derived class?

If that is the case, then I see the use of "virtual." The point of my question was I did not see a way to use dynamic binding without declaring multiple objects. Now, if null_pointer made a good pointer. If he said what I think he said, then I understand a use for "virtual." I still have to declare multiple class, however, through pointer there is little resource loss and performance reduction.

Kuphryn

Share this post


Link to post
Share on other sites
kuphryn    210
I see the big picture is sometimes "virtual" is a necessity. It is not an option in some cases where the program has no way of knowing which function you are implying.

So the question came down to the performance of static binding and dynamic binding. If there is no performance difference, then I think it is better to just use dynamic binding with inheritance.

Kuphryn

Edited by - kuphryn on December 31, 2001 3:15:02 PM

Share this post


Link to post
Share on other sites
null_pointer    289
Let me try to clear this up. I made two different and indirectly related points. First things first: inheritance and storage. Forget pointers for a little while.

When you instantiate a class that has no ancestors, you get one object. That is it. When you instantiate a class that has ancestors, you get an object of the class plus objects of each and every ancestor. These "subobjects" are implicit and you have basically no control over whether or not they are created; when you specified inheritance you brought along all those "subobjects" as well.

Let us look at the following examples, and I will tell you what is going on. The numbers to the left of each line are line numbers, and are only there for the sake of reading the example code.

  
01 class base {};
02 class derived1 : public base {};
03 class derived2 : public derived1 {};
04  
05 int main()
06 {
07 // very simple declarations, no pointers!

08  
09 base instance1;
10 derived1 instance2;
11 derived2 instance3;
12  
13 return 0;
14 }


"base" has no ancestors and so on line 09 when you instantiate "base," all you get is one object: an instance of "base." Very simple.

"derived1" has one ancestor, which itself has no ancestors, which makes a total of one ancestors. "instance2" actually has two objects: "derived1" and "base." The "base" object is hidden and implicitly created, and the only way to access it from outside "derived1" is to cast it. (Line 10 has no effect on instance1.)

"derived2" has one ancestor, which itself has one ancestor, which makes a total of two ancestors. "instance3" actually has three objects: "base," "derived1," and "derived2." The "base" and "derived1" objects are hidden and implicitly created, and the only way to access them from outside "derived2" is to cast them. (Line 11 has no effect on instance1 or instance2.

Another way of saying this is that each "derived2" contains a "derived1," and each "derived1" contains a "base."

Now if you put output in the constructors and destructors for each of the classes, and kept the same main(), you would get a total of six constructor calls and six destructor calls because there are a total of six objects:


instance1: 1 object
instance2: 2 objects
instance3: 3 objects
--------------------
total: 6 objects


Once you understand this concept, pointers become very simple.

A pointer is just a variable that holds the address of an object. At the machine code level, a pointer is most likely just four bytes - the same size as an int. Pointers do not have constructors or destructors, either, and their operations translate almost directly into machine code instructions, so compared to user-defined types such as classes, pointers have very little overhead.

Now that we have that down, let us expand the example with three extra lines in main():

  
01 class base {};
02 class derived1 : public base {};
03 class derived2 : public derived1 {};
04  
05 int main()
06 {
07 // very simple declarations, no pointers!

08  
09 base instance1;
10 derived1 instance2;
11 derived2 instance3;
12  
13 // declare some pointers, no objects created here!

14  
15 base* p1 = &instance1;
16 base* p2 = &instance2;
17 base* p3 = &instance3;
18  
19 return 0;
20 }


Now what happens on lines 15, 16, and 17?

On line 15, we declare a pointer to the object called "instance1" by assigning it the address of "instance1". The "&" operator used in this way is the "address-of" operator, and it returns the address of "instance1". We are not creating any objects except for the tiny little pointer, "p1".

On line 16, the same thing is happening. Although you might say, "Wait a minute! ''instance2'' is of type ''derived1'' We cannot assign its address to a ''base*''!" this behavior is logical, because "instance2" implicitly contains a "base" object. Remember the earlier discussion? So again, in line 16 we are not creating any instances of "base," "derived1," or "derived2" - we are simply creating a tiny little pointer, "p1" and making it refer to "instance2".

On line 17, the same thing is happening. We are simply declaring a little four-byte value and making it point to the "base" object within "instance2".

Now let us look at a variation on the above:

derived2* p = new derived2; 


In this example, the green-colored code executes first and creates an instance of "derived2" dynamically. Then the yellow-colored code creates a pointer (p) and assigns the new object''s address to it. We are only creating one instance of "derived2", not two instances.

That code is functionally equivalent to this code:

derived2 instance; 


...in the sense that both create one instance of "derived2".

(Now I hope that helps...)

Share this post


Link to post
Share on other sites
null_pointer    289
quote:
Anonymous Poster

Casting is not involved either, not one bit. Any example that shows casting is using it incorrectly.


Technically, no. Casting is always involved; I think that you are referring to explicit casting, ala dynamic_cast. The following is an explicit cast:

  
base* p = new derived;
 
p->member_function_1();
p->member_function_2();


The above line containing the pointer declaration contains an implicit cast, because "new" returns a pointer of type "derived*" when you create a "derived" object. (Although, if you implement operator new you will find that it returns void*, this is because the compiler handles the constructor calls and type-checking for the implementer, and it does not change the fact that "new" returns a typed pointer.)

Share this post


Link to post
Share on other sites
kuphryn    210
null_pointer:

Thanks! Your point that declaring an object of a class that is in a hierarchy also instantiates all classs from that class to its base is new to me. Maybe I missed it, but I do not remember seeing it in Deitel & Deitel or Stroustrups''. Thanks again.

----------------
while (?)
{
ClassX *CX = new ClassX;
...
delete CX;
}
----------------

Let say there is a while that creates object CX from ClassX and deletes it at the end of the loop. A specific condition must be true to exit the loop. Let say the loop runs exactly three times, which means there are three instances of new ClassX and delete CX. Does that means there are there will be *three different* memory space allocated for CX even after CX is gone?

delete CX; // point CX is gone, but how about "new ClassX"

I am a little confused about:

----------------------------
Now let us look at a variation on the above:


derived2* p = new derived2;


In this example, the green-colored code executes first and creates an instance of "derived2" dynamically. Then the yellow-colored code creates a pointer (p) and assigns the new object''s address to it. We are only creating one instance of "derived2", not two instances.

That code is functionally equivalent to this code:


derived2 instance;
-----------------------------

You made it look like in:

ClassX *CX = new ClassX;

"new ClassX" *is not* deleted in:

delete CX;

It is just "CX" that is gone.

That is where I am confused from your explanation.

Kuphryn

Share this post


Link to post
Share on other sites
Zipster    2359
quote:
The following is an explicit cast:

      base* p = new derived;

p->member_function_1();
p->member_function_2();

quote:
The above line containing the pointer declaration contains an implicit cast,...


You appear to have contradicted yourself. I believe you meant "implicit" for both.

Edited by - Zipster on December 31, 2001 9:20:52 PM

Share this post


Link to post
Share on other sites
null_pointer    289
Zipster:

I am not sure that I understand what you are saying. Are you responding this:


quote:
Original post by null_pointer

(Although, if you implement operator new you will find that it returns void*, this is because the compiler handles the constructor calls and type-checking for the implementer, and it does not change the fact that "new" returns a typed pointer.)


When I said "operator new" I was referring to the C++ operator that you overload, and when I said "new" I was referring to the C++ new expression. They are not the same thing. The expression refers to the low-level object initialization (such as the virtual function table pointer initialization), constructors, and the type checking, none of which are handled by the "operator new" that you may overload:

  
#include <cmalloc>
#include <stdexcept>
 
using std::malloc;
using std::bad_alloc;
 
// find the low-level initialization, constructor call(s), and type-checking in here, if you can:

 
void* operator new (size_t size)
{
if( !size ) size = 1;
 
new_handler h = set_new_handler(0);
set_new_handler(h);
 
for( ;; )
{
if( !p )
{
void* p = malloc(size);
 
if( p ) return p;
else if( h ) h();
else throw bad_alloc();
}
}
}


Edited by - null_pointer on December 31, 2001 10:58:40 PM

Share this post


Link to post
Share on other sites
EvilCrap    134
Casting is a compile-time thing. normally, for the compiler to write code, it needs to look at the data-type. when types are contradictory, the compiler tells you. by casting, you tell the compiler that its cool, and that it can proceed.

its simply a naming/size-recognition system. casting does not allocated more or less space.

class a = (a) new b;
//tell the compiler that b and a are similar enough to copy

Share this post


Link to post
Share on other sites
Guest Anonymous Poster   
Guest Anonymous Poster
null_pointer: I don''t see how that is considered a cast, I have never ever seen anyone call it a cast. Casting is where you tell the compiler to look at data in a new way, because in that particular case you know more than the compiler about the object''s type. Also I think you are confusing kuphryn with a lot of implementation information that isn''t relevant to his question.

kuphryn: virtual functions and inheritance exist to make something called polymorphism work. Because of a limitation in C++ (a fairly reasonable one) you can only get objects to behave polymorphically if you use pointers or references.

Ok here''s an example. We''re going to make a little controller class to control a creature in your game. Assume that we have some constants or enums of type called Action. Every turn the creature checks whatever is controlling it to see what it should do.

  
class Controller //the base class

{
virtual Action Decide() = 0;
};

class AgressiveAI : public Controller
{
Action Decide()
{
if(PlayerAhead()) return CHARGE;
if(PlayerInRange()) return HACK_AND_SLASH;
else return GROWL;
}
};

class PassiveAI : public Controller
{
Action Decide()
{
if(PlayerAhead()) return TURN_AND_FLEE;
if(PlayerInRange()) return COWER;
else return PICK_DAISYS;
}
};

class PlayerControlled : public Controller
{
Action Decide()
{
if(LeftClick()) return HACK_AND_SLASH;
if(RightClick()) return CAST_FIREBALL;
if(SPACEBAR_PRESSED) return JUMP;
//etc

}
};


So you could use the same class for three different creatures but they would all behave very differently depending on the value of their pointer to their controller class. The whole point of virtual functions is to allow you to vary behavior. There is no other reason. How can you vary behavior using only one class? You just can''t have variation (using this method) unless you have multiple.

Share this post


Link to post
Share on other sites
kuphryn    210
Thanks everyone. I see a clearer picture of static binding, but most important, virtual functions and dynamic binding now.

Kuphryn

Edited by - kuphryn on January 1, 2002 3:12:53 AM

Share this post


Link to post
Share on other sites
null_pointer    289
Zipster: LOL...man, I must have been really tired. I see what you are saying now. Thanks for correcting me!


quote:
Original post by kuphryn

You made it look like in:

ClassX *CX = new ClassX;

"new ClassX" *is not* deleted in:

delete CX;

It is just "CX" that is gone.

That is where I am confused from your explanation.


"CX" is a pointer that refers to "new ClassX." The C++ "delete" expression takes a pointer and destroys the object it refers to - so long as the object itself was allocated with "new". Together, new and delete allow you to manage the lifetime of the object manually, as opposed to simply declaring objects whose lifetimes are determined by language rules (i.e., their scope in the program).

The following two examples are basically identical in terms of object construction/destruction, because of where the new and delete expressions are placed:

  
// example 1

 
int main()
{
base* p = new derived;
 
// ...

 
delete p;
 
return 0;
}


  
// example 2

 
int main()
{
derived d;
 
// ...

 
return 0;
}


The usefulness of new and delete is that you can place them in different functions or destroy the object before the function has ended, and so on. As long as you store the pointer returned by new, you can pass it to delete to properly destroy the object.

In this situation, the pointer acts somewhat like a claim check at a repair shop. When you bring your item in to get repaired, the clerk gives you a claim check. You need that claim check to refer to the item by proving that you are the one who brought the item into the store. When the item has been repaired, you give the clerk the claim check, pay the bill, and he will give you back the repaired item.

The difference is that when you call "new" you get both a newly constructed object and the pointer and when you call delete you must give it back the pointer so that it knows which object to destroy.

  
base* p = new derived;
delete p;


Take away the pointer, and how do you specify which object should be deleted? The object you created with "new derived" has no name:

  
new derived; // legal, but dumb

delete ; // huh?


Without a pointer you have no way of referring to the object...

Share this post


Link to post
Share on other sites
null_pointer    289
quote:
Original post by Anonymous Poster

I don''t see how that is considered a cast, I have never ever seen anyone call it a cast. Casting is where you tell the compiler to look at data in a new way, because in that particular case you know more than the compiler about the object''s type.


You are correct, and I was not. I do not know why I thought that cast and conversion were the same thing. A cast is where you direct the compiler to perform an explicit conversion; an implicit conversion is (obviously) performed without a cast. Otherwise, it would not be implicit. Good point!


quote:
Original post by Anonymous Poster

Also I think you are confusing kuphryn with a lot of implementation information that isn''t relevant to his question.


How so?

Share this post


Link to post
Share on other sites