class Person {
protected:
string name;
public:
Person() : name("unknown") {};
Person(char *n) : name(n) {};
void Print() const { cout<<name; };
};
class Student : public Person {
private:
float gpa;
public:
Student() : gpa(0) {};
Student(char *name, float GPA) : Person(name), gpa(GPA) {};
void Print() const { cout<<gpa; };
};
As you can see the class Student has its own method Person::Print(void)as well as the complete signature of the
Student::Print(void)Thus, we override the method
The next question is: if we use the following code:
Student student("John Smith", 3.88);
student.Print();
which of these two methods will be invoked? The answer to this question will be - of course, the method of the Stident
class since variable student is of the Student type. However, if we use the following code
Person *person = new Student("John Smith", 3.88);
person->Print();
where we treat an object of the Student class as an object of the class Person, we invoke the method
of the Person class, since the variable person is a pointer to an object of the Person
type.
Since, as we mentioned above, the signatures of the two
Student student("John Smith", 3.88);
student.Person::Print(); // print the name of the student
student.Print(); // print the GPA of the student
We can use the complete name of the method inside methods of the class Student. For example, we can modify the
void Print() const { Person::Print(); cout<<": "<<gpa; };
The instruction In MS Visual C++ you can use the keyword __super (two underscore symbols) to refer to the parent (base) class. For example, the same modification can be done in MS C++ as
void Print() const { __super::Print(); cout<<": "<<gpa; };
If we use a pointer to a base class to store a pointer of a derived class, then we can still access the methods of the derived class explicitly, but in order to do so, we have to convert the pointer to the right type of pointer first:
Person *person = new Student("Bob Tester", 3.90);
person->Print(); // invoking Person::Print()
static_cast<Student*>(person)->Print(); // invoking Student::Print()
Similarly, we can hide data members of the base class by defining data members with the same names in the derived class. To access members of the base class, we need to use their complete signatures. For example,
student.Person::public_data_member
void Student::Print()
{
Person::Print();
cout<<": "<<gpa;
}
void Student::Print(string msg)
{
cout<<msg;
Print();
}
You can notice that these methods have different signatures. Thus, when a compiler sees a method call:
student.Print("\n");
it knows that it should invoke method If we use a code like
Person *p = new Student("John Tester", 3.90);
p->Print();
we by default using the static binding and because of this we use the method
class Person {
protected:
string name;
public:
Person() : name("unknown") {};
Person(char *n) : name(n) {};
virtual void Print() const { cout<<name; };
};
When a C++ compiler sees a virtual function it knows that it needs to use dynamic binding. A class that contains
at least one virtual function is call a polymorphic class.
In object-oriented programming, polymorphism (from the Greek meaning "having multiple forms") is the characteristic of being able to assign a different meaning to a particular symbol or "operator" in different contexts.
For example, the plus sign (+) can operate on two objects such that it adds them together (perhaps the most common form of the + operation) or, as in boolean searching, a + can indicate a logical "and" (meaning that both words separated by the + operator must be present in order for a citation to be returned). In another context, the + sign could mean an operation to concatenate the two objects or strings of letters on either side of the + sign.
A given operator can also be given yet another meaning when combined with another operator. For example, in the C++ language, a "++" following a variable can mean "increment this value by 1". The meaning of a particular operator is defined as part of a class definition. Since the programmer can create classes, the programmer can also define how operators work for this class of objects; in effect, the programmer can redefine the computing language.
Please modify the previous example to have method Print dynamically bounded and run the same code:
Person *p = new Student("John Tester", 3.90);
p->Print();
After making the function Person::Print() virtual this code will invoke the method
However, if we decide to modify class Student and add method PrintAll defined as:
void PrintAll() const { Person::Print(); cout<<": "<<gpa; };
then the following code
Person *p = new Student("John Tester", 3.90);
p->PrintAll();
will generate an error during the compiling:
filename.cpp(line#) : error C2039: 'PrintAll' : is not a member of 'Person'This happens because the compiler cannot find a method PrintAll in the class Person (recall, that in the compilation time p is nothing but a pointer to a class Person). To avoid this kind of errors we can declare an virtual method PrintAll of the class Person with an empty body:
class Person {
...
public:
...
virtual void PrintAll() const {};
...
};
Please find the complete example on the example page.
Person *person = new Student("John Smith", 3.95);
What happens if we delete the object pointed at by the variable person?
delete person;It turns out that only the destructor of the Person class will be executed. To be more specific, the following classes
class Person {
protected:
string name;
public:
...
~Person() { cout<<"Destroyng object Person\n"; };
};
class Student : public Person {
private:
float gpa;
public:
...
~Student() { cout<<"Destroying object Student\n"; };
};
will generate the output:
Destroying object PersonThis happens because person is a pointer to an object of the class Person and using
virtual ~Person() { cout<<"Destroyng object Person\n"; };
Then the output will look like:
Destroying object Student Destroying object PersonThis happens because the variable person will be identified as a pointer to an object of the type Student, the appropriate constructor
As you understand constructors cannot be virtual simply because when we create a new object we know exactly what kind of object we create.
virtual return_type function_name(parameter_list) = 0;
For example, we can design an abstract class for geometric figures. In this class we gather all common characteristics of all figures as well as their common methods (interface):
class Figure {
protected:
int x, y;
static Graphics *rg;
static Pen *erasor;
public:
Figure() : x(0), y(0) {};
Figure(int X, int Y) : x(X), y(Y) {};
virtual ~Figure() {};
void MoveTo(int X, int Y) { x=X; y=Y; };
virtual void Draw(Pen *pen) const = 0;
virtual void Hide() const = 0;
virtual void DrawFilled(Brush *bsrush) const = 0;
...
};
Using this class as a base class we can create derived classes Circle, Square, and
Rectangle.
class Circle : public Figure {
private:
int radius;
public:
Circle() : radius(10) {};
Circle(int x, int y, int r) : Figure(x, y), radius(r) {};
~Circle() {};
void Draw(Pen *pen) const
{
gr->DrawEllips(pen, x, y, radius, radius);
};
void Hide() const
{
gr->DrawEllips(erasor, x, y, radius, radius);
};
void DrawFilled(Brush *bsrush) const
{
gr->FillEllips(brush, x, y, radius, radius);
};
};
Please find the complete project example on the example page.
Figure *obj = new Circle(5, 5, 10); if( typeid(*obj) == typeid(Square) ) cout<<"Yes"; else cout<<"No";Please note that we need to pass the object itself to the typeid() operator, not a pointer to an object. Among other things, the type_info object contains method
std::vector<Figure*> pieces; // randomly initialize them ... for(i=0;i<pieces.size();i++) cout<<typeid( *(pieces[i]) ).name()<<endl;The type_info type is defined in the <typeinfo> header file. Please study the complete program example.
Another way to figure out if an object pointer points to an object of a specific type is to use the dynamic_cast operator. This operator takes the type you want to convert a variable into as a template argument (embraced by < and > signs) and the object to convert as a parenthesized argument. For example:
Figure *f = new Square(5, 5, 10); Square *s = dynamic_cast<Square*>( f );If the dynamic type of the parameter matches the template argument type, the value is returned. If the type is not correct, them the NULL value is returned. We can compare the result with the NULL value to see if the conversion was successful. For example, we can find out if variable f contains a pointer to an object of the Rect type:
Rect *r = dynamic_cast<Rect*>( f ); if( r != NULL ) cout<<"This is not a rectangle";
The static_cast operator is similar, but performs no run-time checks on the result. If the argument cannot be converted to the specified type, no indication will be given. Most of the time you won't even be able to compile the program due to an error message like:
Error E2031 cast.cpp 70: Cannot cast from 'C *' to 'B *' in function main() *** 1 errors in Compile ***However, even if you manage to compile the program, the result of impossible static casting will be unpredictable. For this reason the static_cast should be used for primitive data types or not polymorphic types. Please find the compete example on the example page.
At the end of the chapter we would like to note that only pointers and references are polymorphic in C++, the real objects are not. For example, if we have two object (not references) declared as
Student student("John Smith", 4.0);
Person person;
we can use the following assignment (since Student is a Person):
person = student;but all additional fields of the student object will be cut, and if we use
person.print();then
class Figure {
...
public:
void Display() { Show(); };
virtual void Show() { ... };
};
class Square : public Figure {
...
public:
void Display() { Show(); };
virtual void Show() { ... };
}:
Please answer the questions about the following code:
Figure *fig = new Square(); fig->Display(); dynamic_cast<Square*>(fig)->Display();
fig->Show();