Static Binding & Dynamic Binding :: C++

Started by
17 comments, last by kuphryn 22 years, 3 months ago
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
Advertisement
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.
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
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.
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
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
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
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 objectinstance2: 2 objectsinstance3: 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 = &instance116    base* p2 = &instance217    base* p3 = &instance318   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...)
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.)
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

This topic is closed to new replies.

Advertisement