![]() |
| C++ programming |
Inheritance Refresher
New classes can be derived from existing classes using a mechanism called "inheritance". Classes that are derived FROM are called "base classes" and derived classes are also known as sub-classes. The following is an example:
![]() |
| Inheritance |
class Vehicle
{
private: string Make;
string Color;
...
};
class Car: Vehicle
{
// member list includes Make and Color
// other Car specific members would go here.
};
{
private: string Make;
string Color;
...
};
class Car: Vehicle
{
// member list includes Make and Color
// other Car specific members would go here.
};
In this example, Vehicle is the base class and Car is the derived class. The car automatically inherits the Make and Color properties and is also free to implement its own properties and methods that are specific for a Car.
Types of Inheritance in C++
C++ supports three different forms of inheritance, public, private, and protected. Public inheritance describes how a derived class inherits all the member variables of a base class, both private and public, but is only able to directly access the public members of the base class. All members of the base class will retain their accessibility in the derived class. Let's see an example.
**Person.h**
#pragma once
#include <string>
class Person
{
private:
std::string firstName;
std::string lastName;
int age;
public:
Person();
Person(std::string fName, std::string lName);
Person(std::string fName, std::string lName, int age);
~Person();
void SayHello();
};
**Student.h**
#pragma once
#include "Person.h"
class Student :
public Person
{
public:
Student();
~Student();
};
**Student.cpp**
#include "stdafx.h"
#include "Student.h"
Student::Student()
{
}
Student::~Student()
{
}
// this line will cause a compiler error
firstName = "Tom";
**Main.cpp**
#include "stdafx.h"
#include "Person.h"
#include "Student.h"
int main()
{
Student student1;
// this line will generate a compiler error
student1.firstName = "Tom";
// this line is ok
student1.SayHello();
return 0;
}
In this example we have a Person class. We are only listing the header file for this example so as not to get bogged down in the implementation details just yet. Next, we show the header file for a Student class. Note that the class name Student is followed by a colon and then public Person. This indicates that Student is a derived class of Person using public inheritance. Also note that the Student class contains a default constructor and a destructor. These are separate from the constructor and destructor for the Person class.
In the main.cpp file we declare a Student object called student1. We then attempt to set the firstName variable to a value. That line of code will generate a compiler error even though Student has inherited the firstName member variable from Person. The reason is the public inheritance rule and the fact that the first name member variable is declared as private in Person. We'll cover how to access these variables in a little bit. But notice that the student1.SayHello() function call will work as SayHello() is a public function. Also note that in the Student.cpp class, we attempt to use the firstName variable directly but the compiler will throw an error against this line as well.
At this point you might be asking what use the private member variables are if they can't be accessed in a derived class. The answer is a simple one in that we need to go back and think about encapsulation again. Providing public getters and setters for these private member variables is the proper way to gain access to them from outside the class.
As mentioned, there are three types of inheritance in C++, public, protected, and private. This table explains the differences:
**Person.h**
#pragma once
#include <string>
class Person
{
private:
std::string firstName;
std::string lastName;
int age;
public:
Person();
Person(std::string fName, std::string lName);
Person(std::string fName, std::string lName, int age);
~Person();
void SayHello();
};
**Student.h**
#pragma once
#include "Person.h"
class Student :
public Person
{
public:
Student();
~Student();
};
**Student.cpp**
#include "stdafx.h"
#include "Student.h"
Student::Student()
{
}
Student::~Student()
{
}
// this line will cause a compiler error
firstName = "Tom";
**Main.cpp**
#include "stdafx.h"
#include "Person.h"
#include "Student.h"
int main()
{
Student student1;
// this line will generate a compiler error
student1.firstName = "Tom";
// this line is ok
student1.SayHello();
return 0;
}
In this example we have a Person class. We are only listing the header file for this example so as not to get bogged down in the implementation details just yet. Next, we show the header file for a Student class. Note that the class name Student is followed by a colon and then public Person. This indicates that Student is a derived class of Person using public inheritance. Also note that the Student class contains a default constructor and a destructor. These are separate from the constructor and destructor for the Person class.
In the main.cpp file we declare a Student object called student1. We then attempt to set the firstName variable to a value. That line of code will generate a compiler error even though Student has inherited the firstName member variable from Person. The reason is the public inheritance rule and the fact that the first name member variable is declared as private in Person. We'll cover how to access these variables in a little bit. But notice that the student1.SayHello() function call will work as SayHello() is a public function. Also note that in the Student.cpp class, we attempt to use the firstName variable directly but the compiler will throw an error against this line as well.
At this point you might be asking what use the private member variables are if they can't be accessed in a derived class. The answer is a simple one in that we need to go back and think about encapsulation again. Providing public getters and setters for these private member variables is the proper way to gain access to them from outside the class.
As mentioned, there are three types of inheritance in C++, public, protected, and private. This table explains the differences:
| Type of Inheritance | ||
|---|---|---|
| public | protected | private |
| public members are public in derived class and can be accessed directly by member functions and nonmember functions | public members become protected members in derived class and can be accessed directly by member functions | public members become private in derived class and can be accessed directly by member functions |
| protected members are protected in derived class and can be accessed directly by member functions | protected members become protected members in derived class and can be accessed directly by member functions | protected members become private in derived class and can be accessed directly by member functions |
| private members are hidden in derived class and can be accessed by member functions though public or protected member functions | private members are hidden in derived class and can be accessed by member functions though public or protected member functions | private members are hidden in derived class and can be accessed by member functions though public or protected member functions |
You can initialize members of the base class by calling the base class constructor from your derived class. It is recommended that you make the call to the base class constructor as the first thing in your derived class to ensure that member variables in the base class are initialized before they are accessed or used in the derived class. The way to call the base class constructor from your derived class is demonstrated here.
Student::Student():Person("Tom", "Thumb")
{
}
Note that immediately following the constructor for Student we see a colon and then a call to the Person constructor that accepts two arguments. You can place any valid constructor here including one that takes no arguments, in which case the default constructor will be called. Make it a good programming practice to call the base class constructor in this manner to ensure your base class is initialized before entering the body of your derived class. Other languages, such as Objective-C, require that the base class is initialized before your derived classes.
For more information, you can see:
C and C++ with Visual Studio: https://aka.ms/edx-dev210.2x-vs02
C and C++ with Visual Studio: https://aka.ms/edx-dev210.2x-vs02
In this video you will understand the Inheritance Refresher briefly.
Encapsulation and Protected Access
The Protected Keyword
When dealing with inheritance in your C++ code, you may find situations where you would like to keep certain members of the base class private to the "outside world" but public to derived classes of that base class. C++ provides the keyword protected, for this purpose.
As an example, let's consider our Person class and some derived class, such as Student. In our existing code samples thus far, we have noted that Student cannot access firstName, lastName, or age directly. This is because the member variables are declared private in Person. We could use the public accessor methods of the Person class to gain access to these private member variables or, we could make those member variables protected instead. By doing so, we can then initialize them directly in our Student class without needing to call the call the accessor methods. Perhaps we want to do this because we want to, as an example, validate age differently for a Student versus a Teacher. We can create custom validation code in the Student or Teacher derived classes and then set the member variable directly for age by making age have protected access.
Perhaps some pure OOP folks might state at this point that we should make the SetAge() accessor a virtual function and force derived classes to override this function, but we'll cover virtual functions in the next topic. For this example, we'll use this scenario to demonstrate protected access. The code changes for Person and Student are listed below.
**Person.h**
#pragma once
#include <string>
class Person
{
private:
std::string firstName;
std::string lastName;
protected:
int age;
public:
Person();
Person(std::string fName, std::string lName);
Person(std::string fName, std::string lName, int age);
~Person();
void SetFirstName(std::string fName);
std::string GetFirstName();
void SetLastName(std::string lName);
std::string GetLastName();
void SayHello();
};
**Person.cpp**
#include "stdafx.h"
#include "Person.h"
#include <iostream>
Person::Person()
{
}
Person::Person(std::string fName, std::string lName)
{
firstName = fName;
lastName = lName;
}
Person::Person(std::string fName, std::string lName, int age)
{
firstName = fName;
lastName = lName;
age = age;
}
Person::~Person()
{
std::cout << "Destructor called as a result of the delete keyword being used" << std::endl;
}
void Person::SetFirstName(std::string fName)
{
this->firstName = fName;
}
std::string Person::GetFirstName()
{
return this->firstName;
}
void Person::SetLastName(std::string lName)
{
this->lastName = lName;
}
std::string Person::GetLastName()
{
return this->lastName;
}
void Person::SayHello()
{
std::cout << "Hello" << std::endl;
}
**Student.h**
#pragma once
#include "Person.h"
class Student :
public Person
{
public:
Student();
~Student();
void setAge(int);
int getAge();
void SayHello();
};
**Student.cpp**
#include "stdafx.h"
#include "Student.h"
#include <iostream>
Student::Student()
{
}
Student::~Student()
{
}
void Student::setAge(int age)
{
if (age < 5)
{
std::cout << "Student age needs to at least 5 years old." << std::endl;
}
else
{
this->age = age;
}
}
int Student::getAge()
{
return this->age;
}
void Student::SayHello()
{
std::cout << "Hey, how's it goin'?" << std::endl;
}
Looking at the Person header file, we note that the member variable age is now listed in the section under protected. You will also notice that the setAge() and getAge() are no longer present in the Person class file. Again, we could have left them in Person and forced or allowed overridden functions in the derived classes but in this instance, we are just demonstrating the use of protected access.
In the Student class, we note that in the setAge() and getAge() functions, we can now access the age member variable directly. To test this on your own computer, copy and paste this code into a C++ project and verify that there are no compilation errors. Then, go back into Person.h and change the age member variable to private and see what errors are generated in the Student functions dealing with age.
As an example, let's consider our Person class and some derived class, such as Student. In our existing code samples thus far, we have noted that Student cannot access firstName, lastName, or age directly. This is because the member variables are declared private in Person. We could use the public accessor methods of the Person class to gain access to these private member variables or, we could make those member variables protected instead. By doing so, we can then initialize them directly in our Student class without needing to call the call the accessor methods. Perhaps we want to do this because we want to, as an example, validate age differently for a Student versus a Teacher. We can create custom validation code in the Student or Teacher derived classes and then set the member variable directly for age by making age have protected access.
Perhaps some pure OOP folks might state at this point that we should make the SetAge() accessor a virtual function and force derived classes to override this function, but we'll cover virtual functions in the next topic. For this example, we'll use this scenario to demonstrate protected access. The code changes for Person and Student are listed below.
**Person.h**
#pragma once
#include <string>
class Person
{
private:
std::string firstName;
std::string lastName;
protected:
int age;
public:
Person();
Person(std::string fName, std::string lName);
Person(std::string fName, std::string lName, int age);
~Person();
void SetFirstName(std::string fName);
std::string GetFirstName();
void SetLastName(std::string lName);
std::string GetLastName();
void SayHello();
};
**Person.cpp**
#include "stdafx.h"
#include "Person.h"
#include <iostream>
Person::Person()
{
}
Person::Person(std::string fName, std::string lName)
{
firstName = fName;
lastName = lName;
}
Person::Person(std::string fName, std::string lName, int age)
{
firstName = fName;
lastName = lName;
age = age;
}
Person::~Person()
{
std::cout << "Destructor called as a result of the delete keyword being used" << std::endl;
}
void Person::SetFirstName(std::string fName)
{
this->firstName = fName;
}
std::string Person::GetFirstName()
{
return this->firstName;
}
void Person::SetLastName(std::string lName)
{
this->lastName = lName;
}
std::string Person::GetLastName()
{
return this->lastName;
}
void Person::SayHello()
{
std::cout << "Hello" << std::endl;
}
**Student.h**
#pragma once
#include "Person.h"
class Student :
public Person
{
public:
Student();
~Student();
void setAge(int);
int getAge();
void SayHello();
};
**Student.cpp**
#include "stdafx.h"
#include "Student.h"
#include <iostream>
Student::Student()
{
}
Student::~Student()
{
}
void Student::setAge(int age)
{
if (age < 5)
{
std::cout << "Student age needs to at least 5 years old." << std::endl;
}
else
{
this->age = age;
}
}
int Student::getAge()
{
return this->age;
}
void Student::SayHello()
{
std::cout << "Hey, how's it goin'?" << std::endl;
}
Looking at the Person header file, we note that the member variable age is now listed in the section under protected. You will also notice that the setAge() and getAge() are no longer present in the Person class file. Again, we could have left them in Person and forced or allowed overridden functions in the derived classes but in this instance, we are just demonstrating the use of protected access.
In the Student class, we note that in the setAge() and getAge() functions, we can now access the age member variable directly. To test this on your own computer, copy and paste this code into a C++ project and verify that there are no compilation errors. Then, go back into Person.h and change the age member variable to private and see what errors are generated in the Student functions dealing with age.
Friend Functions
A class can define an external function as a friend function. This allows the friend function to access all the members of the class, including private members. Friend functions are non-members, which means they don't receive a "this" pointer. Consequently, they must require an explicit parameter to access an object.
The following example shows how a class can declare an external function named SomeExternalFunction() as a friend function of the class. SomeExternalFunction() receives a reference parameter that identifies the object the function will deal with:
**MyClass.h**
class MyClass
{
friend void SomeExternalFunction(MyClass & targetObject);
// Data members and member functions, as required.
...
};
The following code snippet shows how you might implement SomeExternalFunction(). Typically, you put friend function implementations in the same source file as member function implementations, because they are all part of the semantic meaning of the same class. However you don't prefix friend functions with the ClassName:: syntax, because they are not members of the class:
**MyClass.cpp**
#include "MyClass.h"
void SomeExternalFunction(MyClass & targetObject)
{
// Access any members on the target object, i.e. public, private, or protected members.
...
}
The following example shows how a class can declare an external function named SomeExternalFunction() as a friend function of the class. SomeExternalFunction() receives a reference parameter that identifies the object the function will deal with:
**MyClass.h**
class MyClass
{
friend void SomeExternalFunction(MyClass & targetObject);
// Data members and member functions, as required.
...
};
The following code snippet shows how you might implement SomeExternalFunction(). Typically, you put friend function implementations in the same source file as member function implementations, because they are all part of the semantic meaning of the same class. However you don't prefix friend functions with the ClassName:: syntax, because they are not members of the class:
**MyClass.cpp**
#include "MyClass.h"
void SomeExternalFunction(MyClass & targetObject)
{
// Access any members on the target object, i.e. public, private, or protected members.
...
}
Friend Classes
A class can define another class as its friend. A friend class can access all the members of the first class. This is useful if you have a "binary system", that is two classes that are intimately related to each other.
A good example of friend classes is the "handle-body" idiom in C++. This idiom splits one semantic class into two related class - a "body" class that defines the private data, and a "handle" class that defines the public behavior. The body defines the handle class as a friend class, so that the handle class can access all the members (including private members) in the body class.
The following code listings show how to implement the handle-body idiom in C++ using friend classes.
**Body.h**
class Handle; // Forward reference of the "handle" class, so the compiler knows about it.
class Body
{
friend class Handle;
private:
int someData;
...
};
**Handle.h**
#include "Body.h"
class Handle
{
private:
Body * body; // The "handle" class typically maintains an internal instance of the "body" class.
public:
Handle();
~Handle();
void someOperationOnBody();
...
};
**Handle.cpp**
#include "Handle.h"
Handle::Handle()
{
body = new Body; // Create the underlying "body" object.
}
Handle::~Handle()
{
delete body; // Delete the underlying "body" object.
}
void Handle::someOperationOnBody()
{
someData = 42; // Perform operations on the underlying "body" object.
}
Client code just works with the "handle" class - the client code has no knowledge of the underlying "body" class. Consider the following example:
**Main.cpp**
#include "Handle.h"
int main()
{
Handle h;
h.someOperationOnBody();
return 0;
}
A good example of friend classes is the "handle-body" idiom in C++. This idiom splits one semantic class into two related class - a "body" class that defines the private data, and a "handle" class that defines the public behavior. The body defines the handle class as a friend class, so that the handle class can access all the members (including private members) in the body class.
The following code listings show how to implement the handle-body idiom in C++ using friend classes.
**Body.h**
class Handle; // Forward reference of the "handle" class, so the compiler knows about it.
class Body
{
friend class Handle;
private:
int someData;
...
};
**Handle.h**
#include "Body.h"
class Handle
{
private:
Body * body; // The "handle" class typically maintains an internal instance of the "body" class.
public:
Handle();
~Handle();
void someOperationOnBody();
...
};
**Handle.cpp**
#include "Handle.h"
Handle::Handle()
{
body = new Body; // Create the underlying "body" object.
}
Handle::~Handle()
{
delete body; // Delete the underlying "body" object.
}
void Handle::someOperationOnBody()
{
someData = 42; // Perform operations on the underlying "body" object.
}
Client code just works with the "handle" class - the client code has no knowledge of the underlying "body" class. Consider the following example:
**Main.cpp**
#include "Handle.h"
int main()
{
Handle h;
h.someOperationOnBody();
return 0;
}
For more information , you can see:
C and C++ with Visual Studio: https://aka.ms/edx-dev210.2x-vs02
C and C++ with Visual Studio: https://aka.ms/edx-dev210.2x-vs02
Virtual Functions and Abstract Classes
Introducing Virtual Functions
One of the reasons for using inheritance is to reuse common code across a hierarchy of related classes. For example, if you have similar classes such as Student, Employee, and Contractor, you can factor-out common data members and member functions into a base class such as Person. The Student, Employee, and Contractor classes can then inherit these common members from the Person class.
When you use inheritance, you often find that the derived classes need to implement specialized versions of some member functions from the base class. For example, the Person class might define a display() member function to display a person's name and age, whereas the Student class might want to display the student's course as well. This is known as "overriding".
To define overridable member functions properly in C++, you must prefix the function with the virtual keyword in the base class definition:
class Person
{
private:
std::string name;
int age;
public:
virtual void display() const; // Overrideable function.
...
};
Note that you don't use the virtual keyword when you implement the function in the source file:
void Person::display() const
{
std::cout << name << ", " << age << std::endl;
}
When you use inheritance, you often find that the derived classes need to implement specialized versions of some member functions from the base class. For example, the Person class might define a display() member function to display a person's name and age, whereas the Student class might want to display the student's course as well. This is known as "overriding".
To define overridable member functions properly in C++, you must prefix the function with the virtual keyword in the base class definition:
class Person
{
private:
std::string name;
int age;
public:
virtual void display() const; // Overrideable function.
...
};
Note that you don't use the virtual keyword when you implement the function in the source file:
void Person::display() const
{
std::cout << name << ", " << age << std::endl;
}
For more information , you can see:
C and C++ with Visual Studio: https://aka.ms/edx-dev210.2x-vs02
C and C++ with Visual Studio: https://aka.ms/edx-dev210.2x-vs02
Overriding Virtual Functions
When you define a derived class, you can optionally choose to override some or all of the virtual functions defined in the base class. Note that you don't have to override virtual functions if you don't want to.
To override a virtual function in a derived class, re-declare the function in the derived class header file. The function signature must match that in the base class. You should use the virtual keyword again, to remind anyone reading your code that this is an "overrideable" function - for example, subsequent derived classes can choose to override this function further if appropriate.
The following example shows how the Student class can declare that it overrides the display() function from its base class:
class Student : public Person
{
private:
std::string course;
public:
virtual void display() const; // Override function from base class.
...
};
In the source file for the derived class, implement the new version of the function for your derived class. If you want to leverage the existing functionality of the base-class version of the function, you can call the function using the syntax BaseClassName::FunctionName(). For example:
void Student::display() const
{
// Call base-class version of display(), to display person-related info.
Person::display();
// Now display the student-related info.
std::cout << course << std::endl;
}
To override a virtual function in a derived class, re-declare the function in the derived class header file. The function signature must match that in the base class. You should use the virtual keyword again, to remind anyone reading your code that this is an "overrideable" function - for example, subsequent derived classes can choose to override this function further if appropriate.
The following example shows how the Student class can declare that it overrides the display() function from its base class:
class Student : public Person
{
private:
std::string course;
public:
virtual void display() const; // Override function from base class.
...
};
In the source file for the derived class, implement the new version of the function for your derived class. If you want to leverage the existing functionality of the base-class version of the function, you can call the function using the syntax BaseClassName::FunctionName(). For example:
void Student::display() const
{
// Call base-class version of display(), to display person-related info.
Person::display();
// Now display the student-related info.
std::cout << course << std::endl;
}
Virtual Destructors
If a base class defines one or more virtual functions, then it should also define a virtual destructor. For example, here is a virtual destructor for the Person class:
class Person
{
...
public:
virtual ~Person();
...
};
Person::~Person()
{
// Any destruction code for Person object.
}
By defining a virtual destructor in the base class, you ensure that the correct destructor is always called when you delete an object. We discuss this subject in more detail shortly.
class Person
{
...
public:
virtual ~Person();
...
};
Person::~Person()
{
// Any destruction code for Person object.
}
By defining a virtual destructor in the base class, you ensure that the correct destructor is always called when you delete an object. We discuss this subject in more detail shortly.
The Principle of Substitutability
In common with other object-oriented languages, C++ allows you to create base-class variables and initialize them to point to derived-class objects. Specifically, a C++ pointer of type X can point to an X object or anything inherited from X. Likewise a C++ reference variable of type X can refer to an X object or anything inherited from X.
Here are some examples, using the Person and Student classes:
// A base-class pointer can point to that type of object, or to any derived type of object.
Person * p1 = new Person;
Person * p2 = new Student;
// A base-class reference can refer to that type of object, or to any derived type of object.
Person & r1 = somePersonObject;
Person & r2 = someStudentObject;
Here are some examples, using the Person and Student classes:
// A base-class pointer can point to that type of object, or to any derived type of object.
Person * p1 = new Person;
Person * p2 = new Student;
// A base-class reference can refer to that type of object, or to any derived type of object.
Person & r1 = somePersonObject;
Person & r2 = someStudentObject;
Invoking Virtual Functions
When you invoke a virtual function through a pointer or reference, C++ ensures the "correct" version of the function is invoked - it's the type of the actual object being pointed to that matters, not the static type of the pointer or reference variable itself.
Consider the following example using pointers:
// Declare a pointer of type Person, which actually points to a Student object.
Person * p = new Student;
// Call the virtual display() function. This will call the Student display() function,
// because that's the type of object p points to at run time.
p->display();
The same effect happens when you use references:
// Declare a reference of type Person, which actually refers to a Student object.
Person & r = someStudentObject;
// Call the virtual display() function. This will call the Student display() function,
// because that's the type of object r refers to at run time.
r.display();
When you delete an object, the C++ compiler ensures a destructor is called. If you've defined the destructor as virtual, then the run-time mechanism ensures the correct destructor is called based on the type of the object (rather than the static type of the pointer):
// Declare a pointer of type Person, which actually points to a Student object.
Person * p = new Student;
// Use the object.
// ...
// When you're ready, delete the object. If the destructor is virtual, the type of the object at run time
// determines which destructor is called. In this case, it will be the Student destructor.
delete p;
In this video you will understand the virtual function briefly.
Consider the following example using pointers:
// Declare a pointer of type Person, which actually points to a Student object.
Person * p = new Student;
// Call the virtual display() function. This will call the Student display() function,
// because that's the type of object p points to at run time.
p->display();
The same effect happens when you use references:
// Declare a reference of type Person, which actually refers to a Student object.
Person & r = someStudentObject;
// Call the virtual display() function. This will call the Student display() function,
// because that's the type of object r refers to at run time.
r.display();
When you delete an object, the C++ compiler ensures a destructor is called. If you've defined the destructor as virtual, then the run-time mechanism ensures the correct destructor is called based on the type of the object (rather than the static type of the pointer):
// Declare a pointer of type Person, which actually points to a Student object.
Person * p = new Student;
// Use the object.
// ...
// When you're ready, delete the object. If the destructor is virtual, the type of the object at run time
// determines which destructor is called. In this case, it will be the Student destructor.
delete p;
In this video you will understand the virtual function briefly.


Comments
Post a Comment