Channels ▼
RSS

C/C++

Simulating Polymorphic Operators in C++

Source Code Accompanies This Article. Download It Now.


Version 2: The dynamic_cast Operator

My next version removes the extra virtual methods of version 1 to reduce the memory requirements of the program while keeping the performance levels the same. Keep in mind that my version 1 needed an extra pointer for each and every object. In version 2, I simulate the desired polymorphism by using the C++ Runtime_type_information (RTTI) feature combined with the dynamic_cast operator within the parent's insertion function. The dynamic_cast operator converts a pointer or reference from one object type to another object type when both types are within the same class hierarchy (see C++ for Game Programmers, by Noel Llopis; Charles River Media, 2003). This is the only kind of cast operation in C++ that's checked at runtime. The other type conversions either don't check, or check at compile time. If the runtime conversion between pointer types is not valid, the dynamic_cast operation returns a null pointer (see The C++ Standard Library: A Tutorial and Reference, by Nicolia M. Josuttis; Addison-Wesley, 1999).

My strategy for version 2 is to use the dynamic_cast operator in a parent's function to find out if an object passed to this parent function is actually a child object. In other words, if I'm passed a child object, I process the parent's portion of the object, then I call the child's function to process the child's portion. This processing order mimics the first, polymorphic version of the code. Listing Two is the Employee example for version 2.

ostream& operator<<(ostream& os, const Employee& a Employee)
{
// code to print the Employee class
    if (dynamic_cast<const Manager*>(&a Employee) != NULL)
        os << *(dynamic_cast<const Manager*>(&a Employee));
    return os;
}
Listing Two

Getting back to my initial attempt to use the insertion operator (cout << *pEmp), the Employee class is the parent while the Manager is considered a child. In the Employee's insertion (<<) function, I always print the Employee's private variables. Afterwards, I use the dynamic_cast operator to see if I was passed a Manager object. If so, I then call the Manager's insertion function to handle printing the Manager's private variables. Thus, I've printed the Manager's portion using the Manager's function even though the Employee's function is called. This is how the simulated polymorphism is accomplished.

However, I found that using the dynamic_cast operator in this manner leads to a problem. The error shows up in a nonpolymorphic use of the insertion functions. For example:

Manager m1;
cout << manager1;

Unlike the first example using the << function, this time, the Manager's insertion function is correctly called and passed a Manager object. When inside the Manager's function, I have to deal with the Employee portion of the object. This is typically done by calling the Employee's function from within the Manager's function:

ostream& operator<<(ostream& 
   os, const Manager& aManager)
{
   os << static_cast<const 
   Employee&>(aManager);
// code to print the Manager class
   return os;
}

Unfortunately, this call to the Manager's function leads to an infinite recursive loop between the child and parent functions because they are now calling each other. To solve this problem, I introduced a Boolean flag. This flag is a static function variable because its value must be retained during the nested function calls. Listing Three is the updated version of the Manager's insertion function. It uses the Boolean flag I just discussed to stop the recursive loop problem.

ostream& operator<<(ostream& os, const Manager& aManager)
{
    static bool Manager_printed = false;
    if (Manager_printed == false)
    {
        Manager_printed = true;
        os << static_cast<const Employee&>(aManager);
        Manager_printed = false;        
// code to print the Manager class
    }
    return os;
}
Listing Three

Now, calling the Manager's function directly using the manager1 object from the second example correctly invokes the Employee's insertion function to print the Employee portion of manager1. Keep in mind that the Employee's insertion function then calls the Manager's function, but my new flag prevents the loop from going any further. The flag also prevents the Manager's portion of the object from being printed twice.

While this version correctly deals with being called directly by a Manager object, my original example using an Employee pointer that points to a Manager object has a problem—the Employee's function is called twice. The Employee's function is called first because it's processing an Employee pointer. Because the Employee's function was handed a Manager object, it correctly calls the Manager's insertion function. The problem arises within the Manager's function. The Manager's function should not call the Employee's function in this case. However, there is no way for it to tell that it was called by the Employee's function. To fix this problem, a Boolean flag is also added to the Employee function; see Listing Four.

ostream& operator<<(ostream& os, const Employee& a Employee)
{
    static bool Employee_printed = false;
    if (Employee_printed == false)
    {
// code to print the Employee class
        Employee_printed = true; 
        if (dynamic_cast<const Manager*>(&a Employee) != NULL)
            os << *(dynamic_cast<const Manager*>(&a Employee));
        Employee_printed = false;   
    }
    return os;
}
Listing Four

So, the Manager's insertion function successfully deals with its two possible uses—being called directly by a Manager object, or being called indirectly by the Employee's insertion function. In the first case, it calls the Employee's insertion function to process the Employee's portion of the object. In the second case, the Employee's portion is being processed elsewhere.

Unfortunately, I found out that the executable size of the program grows when RTTI is turned on, so my desire to reduce memory requirements may not work as well as I hoped. To make matters even worse, for the dynamic_cast operation to work, the parent class must have at least one virtual method. Because one virtual method is required, the space for this version is the same as the previous version—one vtable for the class, plus a pointer to the vtable for every object. However, if the class already has a virtual method for some other reason, then additional space is not required for this version to implement polymorphism.


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.
 

Video