Home | Gaming | Programming | Play Online | Contact | Keyword Query
Games++ Games & Game Programming

GAMES++
Games++ Home
Games++ Gaming
Games++ Programming
Beta Testing Games
Free Online Games
Hints & Cheats

BROWSER UTILITIES
E-mail This Page
Add to Favorites

SITE SEARCH

Web Games++

AFFILIATES
Cheat Codes
Trickster Wiki
Game Ratings
Gameboy Cheats
PlayStation Cheats
BlackBerry Games
Photoshop Tutorials
Illustrator Tutorials
ImageReady Tutorials

ADVERTISEMENT

ADVERTISEMENT

Ch 11: Virtual Functions, Runtime Polymorphism... - C++ by Metrowerks

Introduction to C++ by Metrowerks

Introduction to Virtual Functions

Polymorphism, one of the three main attributes of an OOP language, denotes a process by which different implementations of a function can be accessed by the use of a single name. Way back in Lesson 1, we learned that polymorphism also means "one interface, multiple methods."

C++ supports polymorphism both at run-time and at compile-time. The use of overloaded functions is an example of compile-time polymorphism. Run-time polymorphism can be achieved by the use of both derived classes and virtual functions.

Pointers to Derived Types

We learned earlier in this course that a pointer of one type may not point to an object of another type. You'll now learn about the one exception to this general rule: a pointer to an object of a base class can also point to any object derived from that base class.

Similarly, a reference to a base class can also reference any object derived from the original base class. In other words, a base class reference parameter can receive an object of types derived from the base class, as well as objects within the base class itself.

Virtual Functions

How does C++ handle these multiple versions of a function? Based on the parameters being passed, the program determines at run-time which version of the virtual function should be the recipient of the reference. It is the type of object being pointed to, not the type of pointer, that determines which version of the virtual function will be executed!

To make a function virtual, the virtual keyword must precede the function declaration in the base class. The redefinition of the function in any derived class does not require a second use of the virtual keyword. Have a look at the following sample program to see how this works:

#include 
using namespace std;
class bclass {
public:
    virtual void whichone() {
        cout << "bclass\n";
    }
};
class dclass1 : public bclass {
public:
    void whichone() {
        cout << "dclass1\n";
    }
};
class dclass2 : public bclass {
public:
    void whichone() {
        cout << "dclass2\n";
    }
};
int main()
{
    bclass Obclass;
    bclass *p;
    dclass1 Odclass1;
    dclass2 Odclass2;
    // point to bclass
    p = &Obclass;
    // access bclass's whichone()
    p->whichone();
    // point to dclass1
    p = &Odclass1;
    // access dclass1's whichone()
    p->whichone();
    // point to dclass2
    p = &Odclass2;
    // access dclass2's whichone()
    p->whichone();
    return 0;
}

The output from this program looks like this:

bclass
dclass1
dclass2

Study the use of the pointer with the derived types. Notice how the type of the object being pointed to, not the type of the pointer itself, determines which version of the virtual whichone() function is executed.

Virtual Functions and Inheritance

In the study of genetics, it is known that there are some traits that can skip a generation and then suddenly re-emerge a few generations later. In considering the properties of inheritance, the same (sort of!) can be said of virtual functions. Virtual functions are inherited intact by all subsequently derived classes, even if the function is not redefined within the derived class. So, if a pointer to an object of a derived class type calls a specific function, the version found in its base class will be invoked. Look at the modification of the above program. Notice that the program does not define the whichone() function in dclass2.

#include 
using namespace std;
class bclass {
public:
    virtual void whichone() {
        cout << "bclass\n";
    }
};
class dclass1 : public bclass {
public:
    void whichone() {
        cout << "dclass1\n";
    }
};
class dclass2 : public bclass {
};
int main()
{
    bclass Obclass;
    bclass *p;
    dclass1 Odclass1;
    dclass2 Odclass2;
    p = &Obclass;
    p->whichone();
    p = &Odclass1;
    p->whichone();
    p = &Odclass2;
    // accesses dclass1's function
    p->whichone();
    return 0;
}

The output from this program looks like this:

bclass
dclass1
bclass

Now that we know how to use virtual functions, let's examine why virtual functions are useful.

Virtual Functions

As we discussed, virtual functions are one of the attributes of C++ that supports run-time polymorphism.

Uses of Virtual Functions

In large, complex programs, virtual functions allow the programmer to simplify the programming process. If used correctly, the base class will successfully define the interface of its derived classes. However, the program will leave the implementation of this interface up to the derived classes. Therefore, the programmer can use one interface for multiple implementations. This capability also allows for the creation of class libraries, which establish standard interfaces, but allow the programmer to tailor these interfaces to any unique implementation situations that may arise. One of the most popular libraries around is the Microsoft Foundation Classes (MFC) library, which provides the interfaces necessary for programming in the Windows environment. This library frees the programmer from having to reinvent the Windows interfaces, instead allowing him or her to focus on the specific implementation of these interfaces.

Pure Virtual Functions and Abstract Classes

If a derived class does not redefine a virtual function, the base version of that function, called a child object, references it. However, sometimes it is necessary for every derived class to have its own version of a virtual function. Pure virtual functions require each derived class to define its own version of a virtual function. To make a virtual function a pure virtual function, use a definition like the following in the base class:

virtual void funct1() = 0;

If a class is derived from a base class containing the above code, and it does not redefine the function funct1(), the program will not compile and the compiler will report an error.

Early Binding and Late Binding

To end this lesson, we will discuss two terms that you may encounter in your study of C++: early binding and late binding. Early binding simply refers to events that occur at compile-time, and late binding to those that occur at run-time. Overloaded functions fall into the early binding category, while virtual functions belong to the late binding group.

Wow! We're really moving along quickly now. A lot of the pieces are in place for you to begin creating powerful, complex programs. The key is practice! Sit down and create a program that will perform some simple task. Use what you have learned so far to create a program that will perform this task. Then, keep thinking about how you can improve on what you have written and add features that will enhance the performance of your original program. You have a great base (no pun intended!) on which to build your programming skills! However, you still have a lot to learn, and your programming skills will continue to improve.

The const Qualifier

Often in programming you want to protect the data in an object from being modified. In C++ you do that by using the keyword const after the declaration of a function (as well as again in the definition of that function.) This tells the compiler that this function may not modify any data member in the class.

For example:

#include 
using namespace std;
class A {
    int x;
    public:
        A(int k) :x(k) {}
        int get(int i) const;
};
int A::get(int i) const
{
    int answer = x;
    if(i >3) answer = i;
    if( i > 3) x = i; // not allowed
    return answer;
}
    
int main()
{
    A a(2);
    cout << a.get(4);
    return 0;
}

The mutable Qualifier

If you declare an object as const, it is protected that none of its members may be modified. However, C++ allows exceptions to the rule by using the mutable keyword.

For example:

#include 
using namespace std;
class A {
    mutable int x;
    int y;
    public:
        A(int k) :x(k), y(k) {}
        int get(int i);
};
int A::get(int i)
{
    x = i; // allowed
    y = i; // not allowed
    return x+y;
}
    
int main()
{
    const A a(0);
    cout << a.get(4);
    return 0;
}

Static Variables

C++ has a static keyword that puts the variable or function on the heap instead of putting it on the stack. By using the static keyword, you can manage your program's memory better. The static keyword is also a useful tool when used in conjunction with recursion. In this section, you will learn the basics of the static keyword.

Static Variables in a Function

The variables in a function are known as local variables. When the function ends, these variables go out of scope, and are popped off the stack. It is often useful to be able to retain the value of a local variable between function calls. You could create a global variable to do this, but it is not as safe as using a local variable. To achieve the benefits of a global variable without the risk, you create a static variable -- one that remains in memory on the heap after the function ends. You simply use the keyword static before the type declaration. In C, you need to declare the initial value when it is declared. In C++, it will automatically be initialized to zero if you don't set the value. If you are absolutely sure that it will not be used in a C program, you can omit the initialization. If you have any doubt, you should initialize it.

static int var = 0;

A common mistake is to do something like this:

int foo()
{
static int i;
i = 0;
i++;
return i;
}

Of course, every time you call this function, it will return 1 since you are reassigning the value 0 to i every time it is called.

For example:

#include 
enum score{ hits, results };
int tankHit(score tally);
int main()
{
for( int i = 1; i < 10; i++ )
{
if(i%3 && i% 5) tankHit(hits);
}
std::cout << "The tank was hit " 
<< tankHit(results) << " times\n";
return 0;
}
int tankHit(score tally)
{
static int count = 0;
if(tally) return count;
return ++count; 
}

The Output Is:

The tank was hit 5 times.

In this simple program, we generate some numbers (1, 2, 4, 7, 8) to simulate the battlefield for our gunner. We can use the same function for both scorekeeping and results by passing it a flag. If we had several vehicles in our simulated battlefield, it would be much easier to track them using static local variables than global variables, or even other local variables.

You will find that there is much power in a static keyword when using it in object-oriented-programming. Now that you've learned the basics, experiment with static variables in your own code.

Static Class Members

Any member of a class can be modified with the keyword static. When a member of a class is declared to be static, it must also have a global definition somewhere outside of that class. When this occurs, only one copy of that variable exists. That copy is then shared by other objects of that class type. No matter how many objects of a class are created, only one copy of a static member exists. Therefore, all instances of a class share the same static variable.

Also, by declaring a static member for a class, you are not allocating memory for it. Instead, you must provide a global declaration for it outside of the class. To do this, you use the scope resolution operator (::). Look at the following sample:

#include 
using namespace std;
    class sampclass {
        static int var;
    public:
        void setvar(int j) {var = j;}
        void displayvar() {cout << var << '\n';}
};
int sampclass::var;
int main()
{
    sampclass o1;
    sampclass o2;
    o1.displayvar();
    o2.displayvar();
    o1.setvar(23);
    o1.displayvar();
    o2.displayvar();
    return 0;
}

The output from this program is the following:

0
0
23
23

When var in o1 was set to 23, var in o2 was also set to 23. Since the two variables are, in effect, the same, setting its value once will affect all objects of the class.

Namespaces

Every program we create during this course includes the following code:

using namespace std;

However, we have yet to discuss namespaces in any depth. The namespace keyword was created to prevent problems with conflicting names of classes, variables, etc. Because C++ allows you to split your code into multiple files, and as we will soon learn, multiple class modules, problems have arisen as class and function libraries have grown. Conflicting names can cause a function created by the programmer to override a function of the standard library. The use of namespaces allows the names of classes, variables, and functions to reside in separate places -- not all crowded together in the global namespace.

The namespace keyword solves these problems by localizing the functional scope of names declared within a specific namespace. The entire C++ standard library is now defined within its own namespace: std. Name collision has been greatly reduced thanks to the standardization of namespaces. You can create your own namespaces within your program if you foresee problems with conflicting names.

Fundamentals of Namespaces

The namespace keyword essentially isolates a global namespace by creating special, declarative regions. Any object or function defined within a namespace statement is within the scope of that namespace.

Because objects defined within a certain namespace are not visible to objects outside of that namespace, to access a variable, num, within a namespace called ns1, for example, the following declaration would have to appear outside that namespace:

ns1::num = 5;

This process can be tedious if there are several references to the many variables, functions, or objects within a namespace. The using keyword alleviates this problem. There are two primary functions of the using keyword. The first brings all of the members of a namespace into view. The second only brings specific, named members into view:

using namespace ns1;

If the above were declared inside the main() function, for example, main() would be able to access all members of namespace ns1; without the use of an additional reference. In this case, the following would be acceptable:

num = 5;

However, if there are only specific members of a namespace that you wish to bring into view, the using keyword can be used like this:

using ns1::num;

The above code only brings into view one member of the namespace ns1: num. All other members of the namespace would have to carry a reference that uses the scope resolution operator (::).

The std Namespace

The entire C++ standard library is defined by the std namespace. The statement,

using namespace std;

is not required in your programs. If your program requires only limited usage of the C++ standard library, you don't have to use the above statement in your program, though, you will have to refer to standard library functions, such as cout, in the following way:

std::cout << j;

Therefore, if you plan on referencing many members of the C++ standard library within your program, it is suggested that you include the namespace in your main() function with the using directive.

Next Lesson

Copyright © 1998-2007, Games++ All rights reserved. | Privacy Policy