The static type is the type of an object variable as declared at compile time and the dynamic type is the type of an object variable determined at run-time. In most cases the static and dynamic types of an object variable are the same, however, they can differ when we are using pointers to objects. An example of this capability is demonstrated below:
int main() { Account a = Account(35.00, 34234324); Account *ptrA = &a;CurrentAccount b = CurrentAccount(50.0, 12345, 200.0); CurrentAccount *ptrB = &b;
cout << "Displaying ptrA:" << endl; ptrA->display();
cout << "Displaying ptrB:" << endl; ptrB->display();
//ptrB = ptrA; // not allowed
ptrA = ptrB;
cout << "Displaying ptrA again:" << endl; ptrA->display();
}
A full source code example for this is in
CurrentAccount2.cpp
The output from this code segment is shown as in Figure 3.4, “The output from the code segment above.”
To try to explain this further, see the following figures:
Figure 3.5, “The pointers ptrA
and ptrB
have the same static and dynamic types.” shows the code segment after steps 1 and 2 have
taken place. This means that the two objects are created in memory and the two pointers point
at their respective objects, so an Account
pointer points at the
Account
object and the CurrentAccount
pointer points
at the CurrentAccount
object.
Figure 3.6, “Allowed: The pointer ptrA
has static type Account
and dynamic type CurrentAccount
.” shows the state of the application after the 6th step
takes place. The Account
pointer is assigned to the
CurrentAccount
pointer and this is allowed. To understand this, you can think
of the Account
pointer as understanding the display()
,
makeLodgement()
and makeWithdrawal()
methods. So,
we can call any of these methods on the ptrA
and the object that we are calling
these methods on understands all these methods (and more!). This is guaranteed in this case! why? The
CurrentAccount
class is a child of the Account
class and
so has inherited all the methods from Account
. Therefore, calling the methods
of the pointer to the Account
ptrA
works perfectly.
Figure 3.7, “Not Allowed: Pointer ptrB
has static type CurrentAccount
and dynamic type Account
.” shows the assignment of ptrB = ptrA;
which
is not allowed. If this case was allowed then the CurrentAccount
pointer ptrB
would be pointing to an Account
object. But
the CurrentAccount
pointer understands the methods display()
,
makeLodgement()
, makeWithdrawal()
and
setOverdraftLimit()
. So what would happen if the setOverdraftLimit()
was called on the Account
object a
? It does not have
a setOverdraftLimit()
method and so it would fail.
The rule is: An assignment L=R, is legal only if the static type of R is the same or a derived type of the static type of L.
One very important point to note in the code segment above is that when the pointer
ptrA
of static type Account
points at the
CurrentAccount
object and its display()
method is
called, it is the display()
method of the object CurrentAccount
is called, not
the Account
display()
method. You can see this
in the screen grab of the output, as shown in Figure 3.4, “The output from the code segment above.”, where the
overdraft limit is displayed the second time ptrA->display()
is called.
So the method is called on the dynamic type, not the static type.
The ability of a variable to change its dynamic type during execution is a useful
property of C++, allowing programs to be easier to extend and easier to debug. However,
it does have some consequences: Take the last example - The base class Account
and the derived class CurrentAccount
have different implementations
of the display()
method. The dynamic type of the object determines
which display()
method is called. This is called
Dynamic Binding. With the facility of dynamic binding,
CurrentAccount::display()
is called. If there was no such
facility, Account::display()
would always be called.
The notation that I just used for identifying the two display()
methods allowed a clear indication of which display()
method was being
discussed, either the display()
method associated with the
Account
class or the display()
method
associated with the CurrentAccount
class. The "::" is an actual
operator, called the scope resolution operator, and it has very important uses.
For example: The display()
method that was developed for the
CurrentAccount
class displayed the balance, the account number and
the overdraft limit. However, the code used to display the balance and the account number is
the same as that in the parent class Account::display()
method. So code
has been replicated - This is unacceptable under the OOP paradigm as later alterations to this segment
of code must also be replicated.
The scope resolution operator allow this issue to be resolved, for example, the
CurrentAccount
class's display()
method can
be modified to:
void CurrentAccount::display()
{
Account::display();
cout << " And overdraft limit: " << overdraftLimit << endl;
}
Now, the code from the Account
's display()
method
is not being replicated using cut-and-paste, rather it is being called directly. Why is this
so important?
Well, this demonstrates that we are able to call the method that we are over-riding (the
Account
::display()
method) from within the new
method (CurrentAccount
::display()
).
Code does not have to be re-written. Always Good!
Without this feature - If a later modification needs to be made, for example adding an owner
name state, or sort code to the Account
class, then all derived class
display()
methods would have to be updated.
If a "bug" is found, it need only be fixed once.
It becomes much easier to manage the code.
In this case, only 2 lines of code have been replicated, but it could have been 100's of lines of code, with 10 different types of derived classes.
© 2006
Dr. Derek Molloy
(DCU).