Lab 2.2 -classes and objects
Inheritance as a concept
In Object Oriented Programming, inheritance is typically a fundamental concept, and so it is in C++. Remember that object orientation is based on the idea of forming "natural" units of data and code, resembling objects in the "real world" (which can be an imagined real world). In reality, concept are often related - we define things by comparing them to others, we create things by taking existing concepts and adopting them, we classify things as being "similar to X, except aspect Y is different". This is the idea of inheritance in object oriented languages. As usual with computers, it is all tightly defined and works in a specific fashion. In most OO languages (c++ is slightly more powerful, we will come back to that later) the inheritance relationships form a tree, where the child objects inherit from the parent, basically meaning they are the same concept with additional features:

Here, we illustrate the idea that "vehicle" is a very general concept, and "Wheeled" and "Gliding" vehicles are more specific types of it. For wheeled vehicles, we can distinguish the specific concepts of "Car" and "Bicycle". Of course, as you may have noticed, this is not the only possible modelling. If you wonder where actual cars etc. are, remember we still need to create instances.
In terms of prgramming, inheritance means that attributes and methods of parent objects are found in child objects as well. For illustration, some attributes are shown below:
Notice that there may be more and there are also methods. Typically, in class diagrams (that's the proper name of these diagrams) you would not show inherited attributes in the children.

Of course, you may disagree about which attributes are useful, and it certainly depends on the use case.
Inheritance in C++
In C++, inheritance is expressed as follows:
// Base class
class Vehicle {
public:
int length = 5
void honk() {
cout << "Tuut, tuut! \n" ;
}
};
// Derived class
class WheeledVehicle : public Vehicle {
public:
float wheelSize = 28;
};
int main() {
WheeledVehicle myBike;
myBike.honk();
cout << myBike.length + " " + myCar.wheelSize;
return 0;
}
The colon indicates inheritance. Our Bikehere combines the properties of Vehicle and WheeledVehicle. Notice that attributes are public here, so they can be accessed from everywhere. private attributes (and methods) can only be accessed from the class itself. protected can be accessed from child classes, but not from the outside (so a protected attribute of Vehile could be used in WheeledVehicle, a private one could not).
Visibility
Every instance of a class will contain the values for all attributes of the class itself and its parents. private attributes and methods can only be used from the class itself, not from child classes (so private attributes cannot be used in child classes directly, but methods using them can still be used if public). The modifier protected is making attributes/methods available in child classes as well, but not from the outside.
The friend declaration has two meanings: First, in a class another class can be declared as friend. This means the friend class can access private and protected attributes and methods. Notice the declartion must be "started" by the class who wants to expose something: If class A declares B a friend, B can look into A, not the other way round. Second, friend can be used with unimplemented methods. These friend methods can be implemented as global methods and still access private and protected attributes (example was the ostream overload in the last lab).
#include <iostream>
using namespace std;
class Distance {
private:
int meter;
// friend function
friend int addFive(Distance);
public:
Distance() : meter(0) {}
};
// friend function definition
int addFive(Distance d) {
//accessing private members from the friend function
d.meter += 5;
return d.meter;
}
int main() {
Distance D;
cout << "Distance: " << addFive(D);
return 0;
}
Overloading and overriding
We have seen that it is possible to have several methods with the same name, but different parameter lists. This is referred to as overloading. In C++, overloading does not work as a standard with inheritance:
#include <iostream>
using namespace std;
class Base
{
public:
int f(int i)
{
cout << "f(int): ";
return i+3;
}
};
class Derived : public Base
{
public:
double f(double d)
{
cout << "f(double): ";
return d+3.3;
}
};
int main()
{
Derived* dp = new Derived;
cout << dp->f(3) << '\n';
cout << dp->f(3.3) << '\n';
delete dp;
return 0;
}
Here, both calls to f will call the function f(double), even though f(int) is public. If you want to have the base class method visible in Derived, you need:
class Derived : public Base
{
public:
using Base::func;
double f(double d)
{
cout << "f(double): ";
return d+3.3;
}
};
There is also overriding of methods, which means that a class re-implements a method from the base class:
#include <iostream>
using namespace std;
class Base {
public:
void print() {
cout << "Base Function" << endl;
}
};
class Derived : public Base {
public:
void print() {
cout << "Derived Function" << endl;
}
};
int main() {
Derived derived1;
derived1.print();
Base base1;
base1.print();
return 0;
}
Notice the two print methods have the same name and parameters (none here), otherwise it is not overriding. Here, the first call to print will use the implementation from Derived, the second that from Base.
Multiple inheritance
C++ allows multiple inheritance. So a class can be declared as:
class C: public B, public A
Note that many programming languages don't allow that, due to a number of problems which can arise (Java works around this by allowing multiple interfaces, but not inheritance). One problem is the diamond problem: A class inherits from two classes, which both inherit from the same class:
#include<iostream>
using namespace std;
class Person {
// Data members of person
public:
Person(int x) { cout << "Person::Person(int ) called" << endl; }
};
class Faculty : public Person {
// data members of Faculty
public:
Faculty(int x):Person(x) {
cout<<"Faculty::Faculty(int ) called"<< endl;
}
};
class Student : public Person {
// data members of Student
public:
Student(int x):Person(x) {
cout<<"Student::Student(int ) called"<< endl;
}
};
class TA : public Faculty, public Student {
public:
TA(int x):Student(x), Faculty(x) {
cout<<"TA::TA(int ) called"<< endl;
}
};
int main() {
TA ta1(30);
}
This will lead to the constructor of Person being called twice. A virtual keyword avoids this:
class Faculty : virtual public Person {
};
class Student : virtual public Person {
};
Another issue is what happens if both parents have an identical method (with potentially different implementations)?
struct A {
int idA;
void setId(int i) { idA = i;}
int getId() { return idA;}
virtual void foo() = 0;
};
struct B {
int idB;
void setId(int i) { idB = i;}
int getId() { return idB;}
virtual void foo2() = 0;
};
struct AB : public A, public B
{
void foo() override {}
void foo2() override {}
};
If we want to use setId, we have to say which one we want
AB * ab = new AB(); ab->B::setId(10); ab->A::setId(10);
It could be argued that this is not really in line with the idea of object orientation.
Namespaces
Namespaces are an option in C++ to structure code. Identifiers can be used in different namespaces and can still be used:
#include <iostream>
using namespace std;
// first name space
namespace first_space
{
void func()
{
cout << "Inside first_space" << endl;
}
}
// second name space
namespace second_space
{
void func()
{
cout << "Inside second_space" << endl;
}
}
using namespace first_space;
int main ()
{
// This calls function from first name space.
func();
return 0;
}
Notice without the namespaces, we could not have func twice. Members of namespaces can be addressed using the :: operator, or with a using namespace directive, which makes all members of the namespace directly available. Notice cout is in the std namespace. We could either use std::cout, or a using namespace std as above. Scope rules apply for using, so inside a function is only applies to that function etc. Namespaces can be nested, they are then used with multiple ::.
Notice the :: is also used to define methods for a class. This does not mean namespaces and classes are the same, just the operator is reused.
Namespaces can be re-opened:
namespace my_namespace {
int function1();
}
namespace my_namespace {
int function2();
}
This is only one namespace. This also works across files.
In addition, the inheritance can be public, protected or private:
class Base {
public:
int x;
protected:
int y;
private:
int z;
};
class PublicDerived: public Base {
// x is public
// y is protected
// z is not accessible from PublicDerived
};
class ProtectedDerived: protected Base {
// x is protected
// y is protected
// z is not accessible from ProtectedDerived
};
class PrivateDerived: private Base {
// x is private
// y is private
// z is not accessible from PrivateDerived
};
The inheritance modifier "downgrades" the inherited methods, e.g. if inheritance is protected, all public methods/attributes in the class become protected. Notice private members of the base class are not visible in any case.
Inheritance and variables
An instance of a derived class is an instance of its base class as well, and can take its place. So we can do something like this:
class Base
{
private:
int a;
};
class Derived : public Base
{
private:
int b;
};
Base x;
Derived y;
x = y;
Here, y will be copied to x. During this process, only those parts of Derived which come from Base (i. e. a) will be copied. b will not be copied. This is relevant if it comes to arrays - if we have an array of type Base, we cannot put a full Derived object into it (why not?), even though a Derived "is a" Base as well. So a variable of type x is always an object of type x, and any method executed is from x (this will be relevant when we discuss overloading).