Intermediate C++

Builds upon the foundation principles of C/C++, introducing the concept of Object Oriented Programming, the fundamental concept of which is the creation of objects.

 

Objects can be thought of as independent smaller self-contained mini programs that each have their own name, data and logic, and interact with one another to form a whole application.

 

OOP is intended to make thinking about programming closer to thinking about the real world, where an object’s attributes and behaviours are bundled together.

 

The three pillars of OOP are Encapsulation, Inheritance and Polymorphism.

 

This section of the site covers C++ OOP, and also some of the more advanced techniques in C++

Classes and Objects

A class is a user defined abstract data type that further expands upon the concept of a structure but instead of containing just data, it also includes functions.

 

In OO parlance, functions within a class are referred to as methods.

 

The concept of enclosing data and functions together within a single entity is referred to as Encapsulation.

 

Strictly speaking, as far as the compiler is concerned, the only difference between a structure and a class is that of accessibility (aka visibility). A structure's members are all public by default, whereas a class's members are all private by default. However, it is normal programming convention not to mix the two up and therefore structures are generally used for data (or POD: Plain Old Data), and classes are used for objects.

 

A class consists of three elements:

 

  • Identity
    • Name for the class
  • Attributes
    • Data members
  • Methods
    • Function members

 

A class is akin to a blueprint or plan of how to make an object, that declares its attributes (member data types) and methods.

 

A class is declared by use of the keyword class

  • followed by the user defined identity name for the class (akin to a primitive data type, e.g. int)
  • followed by the class members enclosed within a set of { curly braces }
  • followed by optional object identifiers
  • followed by a semi-colon ;

 

Syntax:

class identity {

public:

members ;

private:

members ;

protected:

members ;

} objects ;

 

The keywords public, private and protected are known as the access specifiers, which define the level of visibility of a class's members.

 

Once the class has been declared, an object is declared just like any other data type (e.g. int x;).

 

Extremely simple Class example:

#include <iostream>
using namespace std ;

class Triangle {

public:
	float width ;
	float height ;

	float area(){
		return ((width * height) / 2) ;
	}
} ;

int main () {

	Triangle myTriangle ;

	myTriangle.width = 5 ; //member access using the .dot operator
	myTriangle.height = 6 ;

	cout << "Triangle area: " << myTriangle.area() << endl ;

	return 0;
}

Compile & Run:

Triangle area: 15

 

The above example is overly simplified to show a function being used within a class. Normally all attributes (in this case the two floats on lines 7 and 8) should be private to ensure that only the object itself is responsible for any changes of its own data.

 

An object is said to be instantiated, by declaring a new identifier against the class's identity (i.e. the abstract data type), as per line 17 above.

Encapsulation

The concept of combining attributes and methods (data and functions), collectively referred to as members, into a single named object / entity.

 

Access / visibility of the members is controlled by access specifiers that define what is presented externally (public) and what is kept hidden (private).

 

Since an object should be responsible for its own data, only its public methods should be used to manipulate its data, and it is these public methods that are considered as its interface.

 

Only the barest minimum should be publicly visible and utilised externally.

 

Any invoking object should not care how the requested object’s methods are implemented, all they should care about is presenting the required information via a message to the object’s public interface in the correct format, so that it can receive an expected return or action.

 

Access to an object’s attributes should only be controlled by the object itself, and no other object should directly change an attribute of another object.

 

This concept is known as data hiding.

 

This is akin to the black box model. We don’t care what goes on inside as long as we get what we want from it, providing we supply the correct information to it.

 

So as long as the interface stays the same, the internal methods of the object can be changed or improved as/if required.

 

Reduces complexity and dependencies, so that a change in one place won’t require multiple changes elsewhere.

 

In this example, objectA is instantiated on line 26, we then access its public set method to give values to its private data members, and finally we access those private data members using the public get methods on line 30:

#include <iostream>
#include <string>
using namespace std;

class MyClass{
	private :
		string first, second ;

	public:
		void set(string x, string y){
			first = x ;
			second = y ;
		}

		string getFirst(){
			return first;
		}

		string getSecond(){
			return second;
		}
};

int main() {

	MyClass objectA ;

	objectA.set("Hello, ","World!") ;

	cout << objectA.getFirst() << objectA.getSecond() << endl ;

	return 0;
}
Hello, World!

 

 

Access to the object's private data is completely controlled by the object itself. We cannot directly access (set or get) at its attributes without using its own methods.

Interface Vs Implementation

This concept focuses upon separating what an object does from how it does it.

 

This concept goes hand in hand with encapsulation, data hiding and abstraction in providing the foundations for OOP.

 

Specifically, Interface is the public methods of a class that control how it behaves. Whereas implementation is the actual workings of how it behaves.

 

For instance, slightly modifying the previous example, the class now simply declares its interfaces within the class declaration.

 

The implementation has been defined outside of the class declaration, but is associated via the scope resolution operator ::

 

Thus, this class's interface can be seen on lines 10, 11 and 12, and their according implementations on lines 15, 20 and 24 :

#include <iostream>
#include <string>
using namespace std;

class MyClass{
	private :
		string first, second ;

	public:
		void set(string, string) ;
		string getFirst() ;
		string getSecond() ;
};

void MyClass::set(string x, string y){
	first = x ;
	second = y ;
}

string MyClass::getFirst(){
	return first;
}

string MyClass::getSecond(){
	return second;
}

int main() {

	MyClass objectA ;

	objectA.set("Hello, ","World!") ;

	cout << objectA.getFirst() << objectA.getSecond() << endl ;

	return 0;
}

Compile & Run:

Hello, World!

Messages

Messages are the communication mechanism between objects.

 

Messages enable one object to invoke an action (method) in another object.

 

Messages comprise of three components:

  1. The object being addressed
  2. The method to perform
  3. Any parameters required by the method

Carry on with the previous example, the messages can be seen on line 32:

 

objectA.set("Hello, ", "World!") ;

 

and similarly on line 34 (without parameters):

#include <iostream>
#include <string>
using namespace std;

class MyClass{
	private :
		string first, second ;

	public:
		void set(string, string) ;
		string getFirst() ;
		string getSecond() ;
};

void MyClass::set(string x, string y){
	first = x ;
	second = y ;
}

string MyClass::getFirst(){
	return first;
}

string MyClass::getSecond(){
	return second;
}

int main() {

	MyClass objectA ;

	objectA.set("Hello, ","World!") ;

	cout << objectA.getFirst() << objectA.getSecond() << endl ;

	return 0;
}

Compile & Run:

Hello, World!

Abstraction

Abstraction is the concept of focusing on the key elements of an item rather than the unimportant (i.e. pulling out [abstracting] the key features), whilst also hiding the way it's implemented.

 

For example, we all know a motorbike is made up of many objects, but essentially they all have two wheels, a frame, an engine, handle bars and a seat - this is abstracting the main aspects of a motorbike. We don't particularly care how it works internally, as long as it does. We can then refine any particular instance of an object according to the specifics required, i.e. by way of defining sub-classes from super classes that add specific details required for the sub-class object.

 

Similarly, we can apply this concept to programming to create generic Abstract Data Types (or Abstract Base Classes) and call upon its methods for a specific object according to the (data) type of object.

 

This allows focus on the "What" not the "How".

 

The classic example is that of a polygon. We know that it is a shape that has sides and an area. We can declare some abstract information of the shape such as its height and width, and then further refine the shape according to the sub-class to define its area:

#include <iostream>
#include <string>
using namespace std;

class Shape{
	protected :
		int height, width;
	public:
		void set(int, int) ;
} ;

class Rectangle : public Shape {
	public:
		int area() {
			return (height * width) ;
		}
} ;

class Triangle : public Shape {
public:
	int area() {
		return (height * width) / 2 ;
	}
} ;

void Shape::set(int x, int y){
	height = x ;
	width = y ;
}

int main() {

	Rectangle myRect ;
	myRect.set(4,5) ;
	cout << "Rectangle has an area of: " << myRect.area() << endl ;

	Triangle myTri ;
	myTri.set(4,5) ;

	cout << "Triangle has an area of: " << myTri.area() << endl ;

	return 0;
}

Compile & Run:

Rectangle has an area of: 20
Triangle has an area of: 10

Access Specifiers

The access specifiers define the level of visibility of a class's members to other objects.

 

The following keywords are used to define the level of access:

  • public
    • public members are accessible any place the object is visible
    • public methods should be the only way to change an object’s attributes
    • public members make up an object’s public interface
  • private
    • private members are accessible only from other members of the same class
      • i.e. once instantiated, only that object
  • protected
    • protected members are accessible from other members of the same class and to members of derived classes derived
      • i.e. a child class can also access the protected members of the parent

The access specifier is declared within a class's codeblock after its opening curly brace { by using one of the above keywords followed with a colon : All members following from that point forward are then considered to fall within the access specifier defined, up to the next access specifier or closing curly brace }

 

The default access specifier for a class is private, and can be omitted - however, it's good programming practice to include for the sake of readability (if only for yourself at a future stage).

 

The general rule is to keep all of your data private and provide public methods to access it. This concept is known as data hiding.

 

public:

With all members defined public, an object's data can be changed without having to use its methods:

#include <iostream>
using namespace std ;

class Triangle {

public:
	float width ;
	float height ;

	//setters
	void setWidth(float x){ width = x ; }
	void setHeight(float y){ height = y ; }
	//getters
	float getWidth(){return width ; }
	float getHeight() {return height; }

	//method
	float area(){
		return ((width * height) / 2) ;
	}
} ;

int main () {

	Triangle myTriangle ;

	myTriangle.setWidth(12) ;
	myTriangle.setHeight(2000) ;
	myTriangle.height = 10 ;

	cout << "myTriangle width was set at " << myTriangle.getWidth() << endl ;
	cout << "myTriangle height was set at " << myTriangle.getHeight() << endl ;
	cout << "myTriangle area: " << myTriangle.area() << endl ;

	return 0;
}

 

Compile & Run:

myTriangle width was set at 12
myTriangle height was set at 10
myTriangle area: 60

 

 

Although the method was called on line 28 to set the height for myTriangle, it was possible to directly access the height on line 29 since all members (including the object's data on lines 7 and 8) are public.

 

 

private:

Now (correctly) defining the class's data as private, we can only access its data by using its methods:

#include <iostream>
using namespace std ;

class Triangle {

private:
	float width ;
	float height ;

public:
	//setters
	void setWidth(float x){ width = x ; }
	void setHeight(float y){ height = y ; }
	//getters
	float getWidth(){return width ; }
	float getHeight() {return height; }

	//method
	float area(){
		return ((width * height) / 2) ;
	}
} ;

int main () {

	Triangle myTriangle ;

	myTriangle.setWidth(12) ;
	myTriangle.setHeight(2000) ;
//	myTriangle.height = 10 ; // does not compile, since height is private

	cout << "myTriangle width was set at " << myTriangle.getWidth() << endl ;
	cout << "myTriangle height was set at " << myTriangle.getHeight() << endl ;
	cout << "myTriangle area: " << myTriangle.area() << endl ;

	return 0;
}

Compile & Run:

myTriangle width was set at 12
myTriangle height was set at 2000
myTriangle area: 12000

 

 

Since the default access specifier level for a class is private, any member not explicitly defined within an access specifier is assumed private.

 

 

protected: 

Allows derived/child class access:

#include <iostream>
using namespace std ;

class Shape {

protected:
	float height ;
} ;

class Triangle:Shape {  //if no access specifier is declared the default of 'private' is used

private:
	float width ;

public:
	//setters
	void setWidth(float x){ width = x ; }
	void setHeight(float y){ height = y ; }
	//getters
	float getWidth(){return width ; }
	float getHeight() {return height; }

	//method
	float area(){
		return ((width * height) / 2) ;
	}
} ;

int main () {

	Triangle myTriangle ;

	myTriangle.setWidth(12) ;
	myTriangle.setHeight(2000) ;

	cout << "myTriangle width was set at " << myTriangle.getWidth() << endl ;
	cout << "myTriangle height was set at " << myTriangle.getHeight() << endl ;
	cout << "myTriangle area: " << myTriangle.area() << endl ;

	return 0;
}

Compile & Run:

myTriangle width was set at 12
myTriangle height was set at 2000
myTriangle area: 12000

 

In this overly simplified example, the height attribute for the child Triangle class is derived from the parent Shape class in which the child is inheriting the protected height data attribute.

getters/setters

Getters and setters are the common names for what is more formally known as the Accessors and Mutators, respectively, which provide the methods to set and get values for an object.

 

Since we want to keep all data private to the object, we need specific methods defined to allow an object to control how its data is accessed.

 

The general approach is to prefix the method names with set / get as per their required functionality:

e.g.

setHeight()... //to set the height attribute

getHeight()... //to get the height attribute

 

Since the scope of data members is local to the class (i.e. within its curly braces {}), values can be assigned to them by passing in a parameter to a class's method specifically defined for this purpose. Assuming a float height ; private member has been declared, we can set its value as follows:

 

void setHeight(float myVar) {

height = myVar ;

}

 

As we are simply setting a value and expect no return, we use the void data type for the method. It is then followed by the setHeight identifier for the method and the expected parameter data type and identifier to be used within the body. The function then simply assigns the passed in myVar variable to the previously declared private member variable height.

 

Similarly, to get a value, a method is defined that simply returns the desired value:

 

float getHeight(){

return height ;

}

 

The data type of the expected return value is declared for the getter, which is then followed by the getHeight identifier for the method whose body simply returns that value.

 

Continuing with the previous examples, we have already seen the getter and setter methods being used:

#include <iostream>
using namespace std ;

class Triangle {

private:
	float width ;
	float height ;

public:
	//setters
	void setWidth(float x){
		width = x ; 
	}
	void setHeight(float y){
		height = y ;
	}
	//getters
	float getWidth(){
		return width ;
	}
	float getHeight(){
		return height;
	}

	//method
	float area(){
		return ((width * height) / 2) ;
	}
} ;

int main () {

	Triangle myTriangle ;

	myTriangle.setWidth(12) ;
	myTriangle.setHeight(2000) ;

	cout << "myTriangle width was set at " << myTriangle.getWidth() << endl ;
	cout << "myTriangle height was set at " << myTriangle.getHeight() << endl ;
	cout << "myTriangle area: " << myTriangle.area() << endl ;

	return 0;
}

Compile & Run:

myTriangle width was set at 12
myTriangle height was set at 2000
myTriangle area: 12000

methods

In OO parlance, the term method is used to denote a function within a class.

 

If a class 's methods were to become increasingly large it could make the code difficult to read, and it is therefore common practice to declare prototypes for the methods inline (i.e. within the class) and to then define the implementation of the methods outside of the class's body, to then be accessed using the :: scope resolution operator.

 

#include <iostream>
using namespace std ;

class Triangle {

private:
	float width ;
	float height ;

public:
	void setWidth(float) ;  //setter prototype
	void setHeight(float) ;
	float getWidth() ;      //getter prototype
	float getHeight() ;
	float area() ;          //method prototype

} ;

void Triangle::setWidth(float x){ //REMEMBER TO DECLARE RETURN TYPE
	width = x ;
}
void Triangle::setHeight(float y){ //REMEMBER TO DECLARE RETURN TYPE
	height = y ;
}
float Triangle::getWidth(){        //REMEMBER TO DECLARE RETURN TYPE
	return width ;
}
float Triangle::getHeight(){       //REMEMBER TO DECLARE RETURN TYPE
	return height ;
}
float Triangle::area(){            //REMEMBER TO DECLARE RETURN TYPE
	return ((width * height) / 2) ;
}

int main () {

	Triangle myTriangle ;

	myTriangle.setWidth(12) ;
	myTriangle.setHeight(2000) ;

	cout << "myTriangle width was set at " << myTriangle.getWidth() << endl ;
	cout << "myTriangle height was set at " << myTriangle.getHeight() << endl ;
	cout << "myTriangle area: " << myTriangle.area() << endl ;

	return 0;
}

Compile & Run:

myTriangle width was set at 12
myTriangle height was set at 2000
myTriangle area: 12000

Constructors

A constructor is a special type of class method that is always called when an object is instantiated.

 

Typically used to set initial values for an object.

 

A constructor has exactly the same name as the class but has no return type!

 

A constructor cannot be explicitly called. It is automatically called when an object is instantiated.

 

If no constructor is defined, the compiler will automatically provide one for you. This is similar to declaring to a primitive data type (e.g. an int or a char) whereby storage will be allocated but is not initialised.

 

The previous example now has a (inline) constructor included on line 11:

#include <iostream>
using namespace std ;

class Triangle {

private:
	float width ;
	float height ;

public:
	Triangle(){ //constructor
		width = 45.45 ;
		height = 12.34 ;
	} ;
	void setWidth(float) ;
	void setHeight(float) ;
	float getWidth() ;
	float getHeight() ;
	float area() ;
} ;

void Triangle::setWidth(float x){width = x ;}
void Triangle::setHeight(float y){height = y ;}
float Triangle::getWidth(){return width ;}
float Triangle::getHeight(){return height ;}
float Triangle::area(){return ((width * height) / 2) ;}

int main () {

	Triangle myTriangle ;

	myTriangle.setWidth(12) ;
	myTriangle.setHeight(2000) ;

	cout << "myTriangle width was set at " << myTriangle.getWidth() << endl ;
	cout << "myTriangle height was set at " << myTriangle.getHeight() << endl ;
	cout << "myTriangle area: " << myTriangle.area() << endl ;

	Triangle myDefault ;
	cout << "myDefault width was set at " << myDefault.getWidth() << endl ;
	cout << "myDefault height was set at " << myDefault.getHeight() << endl ;
	cout << "myDefault area: " << myDefault.area() << endl ;

	return 0;
}

Compile & Run:

myTriangle width was set at 12
myTriangle height was set at 2000
myTriangle area: 12000
myTriangle width was set at 45.45
myTriangle height was set at 12.34
myTriangle area: 280.427

 

A new triangle instance called myDefault was declared on line 39. Notice that no values have been assigned and therefore the constructor has set the object's values to those specific in the class.

 

Here's the same code but with the constructor defined outside the class:

#include <iostream>
using namespace std ;

class Triangle {

private:
	float width ;
	float height ;

public:
	Triangle() ;
	void setWidth(float) ;
	void setHeight(float) ;
	float getWidth() ;
	float getHeight() ;
	float area() ;
} ;

Triangle::Triangle(){
	width = 45.45 ;
	height = 12.34 ;
}
void Triangle::setWidth(float x){width = x ;}
void Triangle::setHeight(float y){height = y ;}
float Triangle::getWidth(){return width ;}
float Triangle::getHeight(){return height ;}
float Triangle::area(){return ((width * height) / 2) ;}

int main () {

	Triangle myTriangle ;

	myTriangle.setWidth(12) ;
	myTriangle.setHeight(2000) ;

	cout << "myTriangle width was set at " << myTriangle.getWidth() << endl ;
	cout << "myTriangle height was set at " << myTriangle.getHeight() << endl ;
	cout << "myTriangle area: " << myTriangle.area() << endl ;

	Triangle myDefault ;
	cout << "myDefault width was set at " << myDefault.getWidth() << endl ;
	cout << "myDefault height was set at " << myDefault.getHeight() << endl ;
	cout << "myDefault area: " << myDefault.area() << endl ;

	return 0;
}

Compile & Run:

myTriangle width was set at 12
myTriangle height was set at 2000
myTriangle area: 12000
myTriangle width was set at 45.45
myTriangle height was set at 12.34
myTriangle area: 280.427

Constructors with parameters

It is possible to have as many constructors as you like as long as their signatures are different, and are therefore being overloaded.

 

This allows your parameters to be set at instantiation, rather than a default set.

 

#include <iostream>
using namespace std ;

class Triangle {

private:
	float width ;
	float height ;

public:
	Triangle() ; //default constructor prototype

	Triangle(float x, float y){ //overloaded constructor
		width = x;
		height = y;
	}

	void setWidth(float) ;
	void setHeight(float) ;
	float getWidth() ;
	float getHeight() ;
	float area() ;
} ;

Triangle::Triangle(){ //default constructor definition
	width = 45.45 ; 
	height = 12.34 ;
}
void Triangle::setWidth(float x){width = x ;}
void Triangle::setHeight(float y){height = y ;}
float Triangle::getWidth(){return width ;}
float Triangle::getHeight(){return height ;}
float Triangle::area(){return ((width * height) / 2) ;}

int main () {

	Triangle myDefault(22.22, 31.13) ;
	cout << "myDefault width was set at " << myDefault.getWidth() << endl ;
	cout << "myDefault height was set at " << myDefault.getHeight() << endl ;
	cout << "myDefault area: " << myDefault.area() << endl ;

	return 0;
}

Compile & Run:

myTriangle width was set at 22.22
myTriangle height was set at 31.13
myTriangle area: 345.854

 

 

The default constructor could now be placed within the parametrised constructor to simplify:

#include <iostream>
using namespace std ;

class Triangle {

private:
	float width ;
	float height ;

public:
	Triangle(float x = 45.45, float y = 12.34){ //default constructor
		width = x;
		height = y;
	}
	void setWidth(float) ;
	void setHeight(float) ;
	float getWidth() ;
	float getHeight() ;
	float area() ;
} ;

void Triangle::setWidth(float x){width = x ;}
void Triangle::setHeight(float y){height = y ;}
float Triangle::getWidth(){return width ;}
float Triangle::getHeight(){return height ;}
float Triangle::area(){return ((width * height) / 2) ;}

int main () {

	Triangle myDefault(22.22, 31.13) ;
	cout << "myDefault width was set at " << myDefault.getWidth() << endl ;
	cout << "myDefault height was set at " << myDefault.getHeight() << endl ;
	cout << "myDefault area: " << myDefault.area() << endl ;

	return 0;
}

Compile & Run:

myTriangle width was set at 22.22
myTriangle height was set at 31.13
myTriangle area: 345.854

Constructor Initialisation Lists

Members can also be initialised using initialisation lists.

 

The members to be initialised are defined after the signature of the constructor, which is followed by a colon : and then specifying the members in a comma separated list with the passed in parameter(s) from the signature making up the parameter for the member being initialised:

 

Triangle::Triangle(int x = 45.45, int y = 12.34) : width(x), height(y) {} ;

 

equivalent to:

 

Triangle::Triangle(int x = 45.45, int y = 12.34) {

width = x ;

height = y ;

} ;

 

#include <iostream>
using namespace std ;

class Triangle {

private:
	float width ;
	float height ;

public:
	Triangle(float, float) ; //constructor prototype
	void setWidth(float) ;
	void setHeight(float) ;
	float getWidth() ;
	float getHeight() ;
	float area() ;
} ;

//constructor with initialisation list
Triangle::Triangle(float x = 45.45, float y = 12.34): width(x), height(y) {} ;

void Triangle::setWidth(float x){width = x ;}
void Triangle::setHeight(float y){height = y ;}
float Triangle::getWidth(){return width ;}
float Triangle::getHeight(){return height ;}
float Triangle::area(){return ((width * height) / 2) ;}

int main () {

	Triangle myDefault ;
	cout << "myTriangle width was set at " << myDefault.getWidth() << endl ;
	cout << "myTriangle height was set at " << myDefault.getHeight() << endl ;
	cout << "myTriangle area: " << myDefault.area() << endl ;

	Triangle myTriangle(16.73, 29.84) ;
	cout << "myTriangle width was set at " << myTriangle.getWidth() << endl ;
	cout << "myTriangle height was set at " << myTriangle.getHeight() << endl ;
	cout << "myTriangle area: " << myTriangle.area() << endl ;

	return 0;
}

Compile & Run:

myTriangle width was set at 45.45
myTriangle height was set at 12.34
myTriangle area: 280.427
myTriangle width was set at 16.73
myTriangle height was set at 29.84
myTriangle area: 249.612

Destructors

A destructor is another special class method, complimentary to a constructor, that cleans up resources by deleting the contents in the storage used for an object when it goes out of scope, or when the delete keyword is used on an object.

 

Has the same name as the class, preceded by a tilde ~

Has no return data type.

Takes no arguments.

 

Cannot be explicitly called. It is automatically called when the object goes out of scope.

 

If no destructor is defined, the compiler will automatically provide one for you. However, there might be time when you want to ensure the object has been cleaned, or you might want to carry out a quick exit task such as leaving a message.

 

#include <iostream>
using namespace std ;

class SingleValue{

private:
	int myVar;

public:
	SingleValue() ; //constructor prototype

	~SingleValue() { //destructor
		cout << "See ya later..." << endl ;
	}

	void setValue(int) ;
	float getValue() ;
} ;

SingleValue::SingleValue(){ //constructor
	myVar = 0 ;
	cout << "Hello, World!" << endl ;
}

void SingleValue::setValue(int x){myVar = x ;}
float SingleValue::getValue(){return myVar ;}

int main () {

	SingleValue myValue ;

	myValue.setValue(42) ;

	cout << "myVar = " << myValue.getValue() << endl ;

	return 0;
}

Compile & Run:

Hello, World!
myVar = 42
See ya later...

Copy Constructor & assignment operator=

Used to make a copy of an existing object.

 

 

Copy Constructor

Takes one parameter, which is a reference (i.e. using an & to create an alias) to the object being copied, of the same class data type.

 

The usual signature of a copy constructor is as follows:

 

ClassName::ClassName(const ClassName & source) :

 

 

If a copy constructor is not defined, the compiler provides an implicit copy constructor that provides a member level copy of the source object.

 

This type of member wise copy is also known as shallow copy.

 

For example, given a simple class definition with 3 attributes:

 

class MyClass {

int myInt ;

float myFloat ;

string myString ;

} ;

 

The compiler would provide an implicit copy constructor that is equivalent to:

 

MyClass::MyClass( const MyClass & source) :

myInt( source.myInt ),

myFloat( source.myFloat ),

myString( source.myString )

{ }

 

An initialiser list has been used to define each of the 3 attributes in this example, but of course the compiler implicit copy constructor would copy N attributes as required for the number of object attributes.

 

The use of the keyword const in the copy constructor signature ensures that the passed in reference (alias) to the object will not be changed, thus ensuring the original remains intact and that the copy constructor acts upon the new object without being able to change the original.

 

 

Assignment operator=

Another form of copying is provided by the = assignment operator, whereby a copy of an existing object is assigned to another. In this case, both objects must exist in the first place for the assignment operator to come into effect, otherwise a copy constructor would be invoked as per the above.

 

The usual signature of assignment = operator is as follows:

 

ClassName & operator=(const ClassName & source)

 

 

The assignment operator= is also implicitly provided by the compiler if it has not been defined, and taking the example class above would be equivalent to:

 

MyClass & operator=( const MyClass & source) {

myInt = source.myInt ;

myFloat = source.myFloat ;

myString = source.myString ;

return *this ;

}

 

Here, the assignment operator = is being overloaded and redefined within the body of its code block. The keyword operator followed by the operator to be overloaded (e.g. =) are defined after the return type of the object, which is of course the class name (in this example myClass). The reference to the object being copied is then used as the source, where in the body it assigns the attributes to the new object (in this example just the 3 primitives) and returns the new object itself via the *this self pointer.

 

It must be noted that the copy constructor and the assignment operator, are two of the "Rule of Three" with third being the destructor, whereby if the default (implicit) versions of any of these three needs to be defined, then it is likely that all three need to be defined.

 

As we have seen above, the copy constructor and the assignment operator have different implementations, and are therefore invoked in different ways.

 

The copy constructor is invoked when the object being copied is used as the parameter for a new object definition.

 

e.g. assuming MyClass objectA ; //create a new object called objectA

then MyClassB objectB(objectA) ; //uses objectA as the parameter to be copied to create objectB

 

 

Alternatively, the assignment operator= is invoked when the two objects already exist.

 

e.g. MyClass objectC ; //create a new object called objectC

then assigning an existing object to the new object e.g. MyClass objectC = objectA ;

 

What's actually happening here is that the assignment operator = is being overloaded and objectA is being used as its parameter.

 

This example shows an objectA being instantiated, which is then used in the copy constructor to instantiate objectB. Next, objectC is instantiated with its own attributes and some text is displayed before the operator= assignment is called, then at the start of the operator= assignment and again at the end of the operator assignment. At this point, the newly copied object is returned using the *this self pointer, which actually returns a reference to the object as indicated by the use of the ampersand & being indicated as the return type at the beginning of the signature.

 

To emphasize, an assignment operator returns a reference to the target object. This allows chaining of assignments, e.g. objectC = objectB = objectA ;

 

#include <string>
#include <iostream>
using namespace std;

class MyClass {
private:
	int myInt ;
	float myFloat ;
	string myString ;

public:

	MyClass(int newInt, float newFloat, string newString) :
		myInt(newInt),
		myFloat(newFloat),
		myString(newString)
		{}

	//copy constructor, using initialisation list
	MyClass( const MyClass & source ) :
		myInt( source.myInt ),
		myFloat( source.myFloat ),
		myString( source.myString )
		{ cout << "COPY CONSTRUCTOR " ; }

/*      MyClass( const MyClass & source ) {			//equivalent of above
        myInt =  source.myInt ;
        myFloat = source.myFloat ;
        myString = source.myString ;
	}
*/
	//assignment operator= Note that a reference is being returned
	MyClass & operator=( const MyClass & source) {
		myInt =  source.myInt ;
		myFloat = source.myFloat ;
		myString = source.myString ;
		cout << "ASSIGNMENT 'OPERATOR=' CALLED" << endl ;
		return *this ;
	}

	//the getters
	int getInt(){ return myInt ; }
	float getFloat(){ return myFloat ; }
	string getString(){ return myString ; }

} ;

int main() {

	cout << "objectA is instantiated and contains: " ;
	MyClass objectA(17, 3.1416, "Apollo") ;
	cout << objectA.getInt() << ", " << objectA.getFloat() << ", " << objectA.getString() << endl << endl ;

	cout << "objectB is instantiated via the " ;
	MyClass objectB(objectA) ;
	cout << "and also contains: " ;
	cout << objectB.getInt() << ", " << objectB.getFloat() << ", " << objectB.getString() << endl << endl ;

	cout << "objectC is instantiated and contains: " ;
	MyClass objectC(12, 9.666, "Hammer time!") ;
	cout << objectC.getInt() << ", " << objectC.getFloat() << ", " << objectC.getString() << endl ;
	objectC = objectA ;
	cout << "objectC now contains: " ;
	cout << objectC.getInt() << ", " << objectC.getFloat() << ", " << objectC.getString() << endl << endl ;

	cout << "objectD is instantiated, but since it did not exist before it uses the " ;
	MyClass objectD = objectA ;
	cout << endl << "and we can see it also contains: " ;
	cout << objectD.getInt() << ", " << objectD.getFloat() << ", " << objectD.getString() << endl << endl;

	return 0 ;
}

Compile & Run:

objectA is instantiated and contains: 17, 3.1416, Apollo

objectB is instantiated via the COPY CONSTRUCTOR and also contains: 17, 3.1416, Apollo

objectC is instantiated and contains: 12, 9.666, Hammer time!
ASSIGNMENT 'OPERATOR=' CALLED
objectC now contains: 17, 3.1416, Apollo

objectD is instantiated, but since it did not exist before it uses the COPY CONSTRUCTOR
and we can see it also contains: 17, 3.1416, Apollo

Deep Copy Constructor

A problem exists with the implicit shallow copy constructor since it literally copies everything, including pointers to members.

 

 

Therefore if a copy or the original should go out of scope before the other, then the destructor will destroy the copied values still being pointed to by the remaining copied objects, thus potentially pointing to garbage, which could lead to a program crash. This can be overcome by creating your own copy constructor that properly allocates memory in the copy. This technique is known as deep copy.

 

 

className::className(const className &rhs) {

myVar = new int ; //the use of new here allocates separate memory for the copied object

...code goes here...

}

 

 

The destructor should then clean up the allocated storage:

 

className::~className() {

delete myVar ; //clean storage

myVasr = NULL ; //assign NULL just for safety

}

 

The following program provides a deep copy constructor, utilising new for each objects pointed data:

#include <iostream>
using namespace std ;

class Humanoid{

private:
	int *myPtr;
	static int objectCount ;

public:
	Humanoid(int) ;                         //constructor
	Humanoid(const Humanoid &rhs) ;         //copy constructor
	~Humanoid();                            //destructor
	void setAge(int) ;                      //setter
	int getAge(void) ;                      //getter
} ;

//initialise static member of Humanoid class
int Humanoid::objectCount = 0 ;

Humanoid::Humanoid(int age){
	cout << "Constructor, allocating pointer to address " ;
	myPtr = new int ;
	*myPtr = age ;
	cout << myPtr << " containing " << *myPtr << endl ;
}

Humanoid::Humanoid(const Humanoid &rhs){
	cout << "Copy Constructor, allocating pointer to address " ;
	myPtr = new int ;
	*myPtr = *rhs.myPtr ;
	cout << myPtr << " containing " << *myPtr << " #" << objectCount << endl ;
	objectCount++;
}

Humanoid::~Humanoid(void){
	cout << "Deleting data in " << myPtr << endl ;
	delete myPtr ;
	myPtr = NULL ;
}

void Humanoid::setAge(int age){*myPtr = age ;}

int Humanoid::getAge(void){return *myPtr ;}

void display(Humanoid person){
	cout << "Humanoid is " << person.getAge() << endl ;
}

int main () {

	Humanoid suzannah(25) ;
	display(suzannah) ;

	Humanoid arif(suzannah) ;  //the suzannah object is being used as a parameter
	display(arif) ;            //in the copy constructor

	Humanoid jenny = suzannah ; //the suzannah object is being assigned to the new jenny object
	display(jenny) ;

	suzannah.setAge(36) ;      //the suzannah object has a new value set
	display(suzannah);

	display(jenny);            //showing the new value does not effect the copied object

	return 0;
}

Compile & Run:

Constructor, allocating pointer to address 0x8a1130 containing 25
Copy Constructor, allocating pointer to address 0x8a1140 containing 25 #0
Humanoid is 25
Deleting data in 0x8a1140
Copy Constructor, allocating pointer to address 0x8a1140 containing 25 #1
Copy Constructor, allocating pointer to address 0x8a1150 containing 25 #2
Humanoid is 25
Deleting data in 0x8a1150
Copy Constructor, allocating pointer to address 0x8a1150 containing 25 #3
Copy Constructor, allocating pointer to address 0x8a1160 containing 25 #4
Humanoid is 25
Deleting data in 0x8a1160
Copy Constructor, allocating pointer to address 0x8a1160 containing 36 #5
Humanoid is 36
Deleting data in 0x8a1160
Copy Constructor, allocating pointer to address 0x8a1160 containing 25 #6
Humanoid is 25
Deleting data in 0x8a1160
Deleting data in 0x8a1150
Deleting data in 0x8a1140
Deleting data in 0x8a1130

 

 

*Note: The copy constructor is called each time the display function is called, since it is using the getAge() method which returns the assigned value using the pointer.

this

The keyword this is a pointer to the current object, automatically provided by the C++ language.

 

The value of this is &currentObject, i.e. it is the address of the current instantiated object.

 

Useful for disambiguating identifier names, i.e. where two data members have the same name, the this pointer can be used to refer to the current object being pointed to.

 

private:

int myVar;

public:

void method(int myVar) {

this->myVar = myVar

}

 

In this case the rhs passed in parameter is assigned to the current object's myVar attribute within the scope of the method, which evades ambiguity towards the class's private myVar by use of the this-> pointer.

 

The current object can also be returned by dereferencing *this

 

return *this ;

 

#include <iostream>
using namespace std ;

class Cube {

private:
	int side ;

public:
	//the prototypes
	Cube(int) ;                        //constructor
	float getSide() ;                  //getter
	float vol() ;                      //calc vol method

	void showThisAddress() {           //display address
		cout << this << endl ;
	}
	Cube contrived(){                  //return current object
		return *this ;
	}
} ;

//the implementations
Cube::Cube(int side = 6){this->side = side ;}
float Cube::getSide(){return side;}
float Cube::vol(){return (side * side * side) ;}

int main () {

	Cube myCube ;
	cout << "myCube side: " << myCube.getSide() ;
	cout << ", volume: " << myCube.vol() << endl ;
	cout << "myCube address using this: " ;
	myCube.showThisAddress() ;
	cout << "which should be the same as using &: " << &myCube << endl << endl ;

	Cube boing = myCube.contrived() ;          //using return of above object to asssign
	cout << "boing side: " << boing.getSide() ;
	cout << ", volume: " << boing.vol() << endl ;
	cout << "boing address using this: " ;
	boing.showThisAddress() ;
	cout << "which should be the same as using &: " << &boing << endl << endl ;

	Cube yourCube(5) ;
	cout << "yourCube side: " << yourCube.getSide() ;
	cout << ", volume: " << yourCube.vol() << endl ;
	cout << "yourCube address using this: " ;
	yourCube.showThisAddress() ;
	cout << "which should be the same as using &: " << &yourCube << endl ;

	return 0;
}

Compile & Run:

myCube side: 6, volume: 216
myCube address using this: 0x28ff0c
which should be the same as using &: 0x28ff0c 

 

boing side: 6, volume: 216
boing address using this: 0x28ff08
which should be the same as using &: 0x28ff08

 

yourCube side: 5, volume: 125
yourCube address using this: 0x28ff04
which should be the same as using &: 0x28ff04

 

 

 

 

Another example:

#include <iostream>
using namespace std ;

class Calc {

private:
	int myVar;

public:
	Calc() {myVar = 0;}	//initial value
	Calc& Add(int x) {myVar += x ; return *this;}
	Calc & Sub(int x) {myVar -= x ; return *this;}
	Calc &Mult(int x) {myVar *= x ;	return *this;}
	int getVal() {return myVar;}
};

int main () {

	//declare myCalc object
	Calc myCalc;

	//chained call methods, passing in parameters and using *this to return values
	myCalc.Add(17).Sub(12).Mult(36) ;

	//display current value of myVar
	cout << myCalc.getVal() ;

	return 0;
}

(source)
Compile & Run:

180

Pointers to Classes

A pointer to a class object is declared just like any other data type:

 

className identifier ;  //declare the instance of the object

 

className *myPtr ;  //declare a pointer of the same class as the object

 

myPtr = &identifier ;  //assign the address of the object instance to the pointer

or

className *myPtr = &identifier  //both previous two steps in one

 

Members of classes can be accessed using the shorthand little arrow -> member pointer dereferencing operator (aka member selection operator).

 

#include <iostream>
#include <string>
using namespace std;

class Rider {
	int age, championships ;
	string nationality ;

public:
	void setValues ( int, int, string ) ; //setter

	int getAge() { return age; }
	int getChampionships() { return championships; }
	string getNationality() { return nationality; }

	void hail() { cout << "All hail the " << championships << " times world champion!" << endl << endl ; }

} bayliss ; //note declaration of Rider object called bayliss

void Rider::setValues ( int age, int championships, string nationality) {
	this -> age = age ;  //using this for the current object
	this -> championships = championships ;
	this -> nationality = nationality ;
}

int main() {

	Rider rossi ; //declare Rider object called rossi

	Rider *myPtr = &rossi ;  //assign pointer to rossi object

	myPtr->setValues ( 33, 9, "Italian") ;  //set rossi object values
	cout << "Valentino Rossi is a "  << myPtr->getAge() << " year old " ;  //display rossi object values
	cout << myPtr->getNationality() << " Moto GP Rider, who has won " ;
	cout << myPtr->getChampionships() << " World GP championships." << endl ;
	myPtr->hail() ;  //display rossi object message

	myPtr = &bayliss ;  //reassign the pointer to the bayliss object

	myPtr->setValues ( 43, 3, "Aussie") ;
	cout << "Troy Bayliss is a "  << myPtr->getAge() << " year old " ;
	cout << myPtr->getNationality() << " World SuperBike Rider, who has won " ;
	cout << myPtr->getChampionships() << " World SuperBike championships." << endl ;
	myPtr->hail() ;

	return 0 ;
}

Compile & Run:

Valentino Rossi is a 33 year old Italian Moto GP Rider, who has won 9 World GP championships.
All hail the 9 times world champion! 

 

Troy Bayliss is a 43 year old Aussie World SuperBike Rider, who has won 3 World SuperBike championships.
All hail the 3 times world champion!

 

 

 

 

 

Summary of pointer and class operators:

*x pointed by x
&x address of x
x.y member y of object x
x->y member y of object pointed by x
(*x).y member y of object pointed by x (equivalent to above)
x[0] first object pointed by x
x[1] second object pointed by x
x[n] (n+1)th object pointed by x

Static Attributes

Static attributes refer to the data members of a class that may be required to be declared as static data types. This could be useful, say, if you needed to know how many objects had been created, were in existence, etc.

 

As with other static declarations the keyword static is used prior to the attributes to be made static, which will ensure that only one copy of the static member exists despite how many objects are made from that class.

 

Static data members of a class are also known as "class variables", because there is only one unique value for all the objects of that same class. Their content is not different from one object of this class to another.

 

Thus a static attribute is shared by all objects of the class and is initialised to zero, should no other initialisation be defined.

 

Static attributes also need to be initialised outside of the class, using the double colon :: scope resolution operator to identify which class it belongs to.

 

#include <iostream>
using namespace std ;

class Humanoid{

private:
    static int qtyHumans ;

public:
    Humanoid(int) ;
    ~Humanoid() ;
} ;

//initialise static member of Humanoid class
int Humanoid::qtyHumans = 0 ;

Humanoid::Humanoid(int age){
    cout << age << " year old object #" << qtyHumans++ << " created!" << endl ;
}

Humanoid::~Humanoid(void){
    cout << "Object #" << --qtyHumans << " destroyed!"<< endl ;
}

int main () {

    Humanoid simon(25) ;
    Humanoid penny(32) ;
    Humanoid jimmy(63) ;

    return 0;
}

Compile & Run:

25 year old object #0 created!
32 year old object #1 created!
63 year old object #2 created!
Object #2 destroyed!
Object #1 destroyed!
Object #0 destroyed!

Static methods

Just as a class's data can be made static, so can its methods.

 

In this respect they act like global functions and are accessed like object members of a class.

 

Since static methods are members of the whole class and not of any instance of an object, they can:

  • only use a class's static data
  • not use non-static members
  • not use the keyword this, as it uses a pointer to an object
  • be called independently, even if no object exists
  • be accessed using the scope resolution operator ::

 

If access to a class's private data member is required, we would normally write a public accessor (get) method. However, this would require instantiation of a class object to access the function. Therefore in the case of static data, we write a public static method to return the static data.

 

#include <iostream>
using namespace std ;

class Humanoid{

public:
	static int qtyHumans ;
	Humanoid() ;
	~Humanoid() ;
    static int getCount() ;
} ;

//initialise static member of Humanoid class
int Humanoid::qtyHumans = 0 ;

int Humanoid::getCount(){return qtyHumans ;}

Humanoid::Humanoid(){
	cout << "Object #" << qtyHumans++ << " created!" << endl ;
}

Humanoid::~Humanoid(void){
	cout << "Object #" << --qtyHumans << " destroyed!"<< endl ;
}

int main () {

	//be sure to use the () parentheses when using a method
	cout << "In the beginning there were " << Humanoid::getCount() << " Humanoids!"<< endl ;
	cout << "But they bred..." << endl ;
	Humanoid simon ;
	Humanoid penny ;
	Humanoid jimmy ;
	Humanoid julia ;
	cout << "Creating " << Humanoid::getCount() << " Humanoids!"<< endl ;
	cout << "So we killed them..." << endl  ;

	return 0;
}

Compile & Run:

In the beginning there were 0 Humanoids!
But they bred...
Humanoid #0 created!
Humanoid #1 created!
Humanoid #2 created!
Humanoid #3 created!
Creating 4 Humanoids!
So we killed them...
Humanoid #3 destroyed!
Humanoid #2 destroyed!
Humanoid #1 destroyed!
Humanoid #0 destroyed!

 

 

Or even simpler, not even creating any objects and just using the static method:

#include <iostream>
using namespace std ;

class Humanoid{

private:
	static int qtyHumans ;

public:
    static int getCount(){
    	return ++qtyHumans ;
    } ;
} ;

//initialise static member of Humanoid class
int Humanoid::qtyHumans = 0 ;

int main () {

	for (int i=0; i<6; i++){
		cout << "Accessing static method " << Humanoid::getCount() << " times" << endl ;
	}

	return 0;
}
Accessing static method 1 times
Accessing static method 2 times
Accessing static method 3 times
Accessing static method 4 times
Accessing static method 5 times
Accessing static method 6 times

Friend Functions

Prototypes of friend functions are declared within a class preceded by the keyword friend and then followed by the function prototype signature, to allow external functions (i.e. the friends) to access private and protected members.

 

Syntax:

friend void display() ;

 

This will allow the display() function, defined outside the class, to access all members within the class:

#include <iostream>
#include <string>
using namespace std;

class Rider {
	string name ;
	int age, wins ;

	friend void display(Rider &) ;  //declaring the friend function

public:
	void setValues (string, int, int) ; //setter

	string getName() { return name; }  //getters
	int getAge() { return age; }
	int getChampionships() { return wins; }

	void hail() { cout << "All hail the " << wins << " times world champion!" << endl << endl ; }

} bayliss ; //note declaration of Rider object called bayliss

void Rider::setValues ( string name, int age, int wins) {
	this -> name = name ;  //using this for the current object
	this -> age = age ;
	this -> wins = wins ;
}

//implementation of the display() friend function
void display(Rider &rider){
	//note the direct private attributes are being referred to, not the methods to get the values!
	cout << rider.name << " is a " << rider.age << " year old motorcyclist, " ;
	cout << "who has won " << rider.wins << " world championships!" << endl ;
}

int main() {
	Rider rossi ; //declare Rider object called rossi
	Rider *myPtr = &rossi ;  //assign pointer to rossi object
	myPtr->setValues ("Valentino Rossi", 34, 9) ;  //set rossi object values
	display(*myPtr) ; //call display function passing in pointer to rossi object
	myPtr->hail() ;  //display rossi object class message

	myPtr = &bayliss ;  //reassign the pointer to the bayliss object
	myPtr->setValues ( "Troy Bayliss", 43, 3) ;
	display(*myPtr);
	myPtr->hail() ;

	return 0 ;
}

Compile & Run:

Valentino Rossi is a 34 year old motorcyclist, who has won 9 world championships!
All hail the 9 times world champion! 

 

Troy Bayliss is a 43 year old motorcyclist, who has won 3 world championships!
All hail the 3 times world champion!

Friend Classes

Whole classes can also be made into friends to grant full access to another class's protected and private members, by declaring the keyword friend followed by the keyword class and then the identifier name of the class you wish to make into a friend.

 

Syntax:

friend class classOne ;

 

This will allow all members within the class in which it is declared to be accessed by the friend classOne.

 

The friendship is not mutual, and just because the original class may have declared its friend it does not mean that it will be able to access members of the friend, unless the friend has explicitly reciprocated the friend declaration back to it. All friends must be explicitly specified, and they do not transfer between one another.

 

#include <iostream>
#include<string>
using namespace std;

class classA {

	private:
		string secret ;
		friend class classB;  //declare a friend class

	public:
		classA() : secret("precious"){ }  //using initialiser list to assign value to secret
		void printMe() { cout << secret << endl; }
};

class classB {
	public:
		void change( classA &changeMe, string toChange ){
			changeMe.secret = toChange; //note classA's secret is being directly accessed
		}
};

int main() {
	classA master;  //create object of classA data type
	classB servant; //create object of classB data type

	master.printMe(); //use method in classA

	servant.change( master, "rotten" ); //use classB method to change classA value

	master.printMe();
}

Compile & Run:

precious 

rotten

Inheritance

Process of creating a new class from an existing class.

 

New class is know as the Derived, or Child or Subclass.

 

Existing class from which it was derived from is know as the Base, or Parent or Superclass.

 

The most important feature of inheritance is that derived class is a type of the base class - the emphasis being on type!

 

 

 

*Note: In UML (Unified Modelling Language), Inheritance is shown as a large blank arrow head

 

Promotes code reuse and reduces code size.

 

Allows us to identify some shared members between objects.

 

Overriding Parent methods for specific Child behaviour.

 

Identify commonality, put into Parent, then let Child classes inherit.

 

This technique implements what is referred to as the "Is-A" relationship.

e.g.

a car is a vehicle

a Ferrari is a car

Therefore a Ferrari is also a vehicle

 

 

A class inherits from another by using a colon : in the declaration of the derived class, like so:

 

class childClass : public parentClass {

... code goes here...

}

 

Notice the use of the keyword public in the declaration, this instructs the compiler that the public members of the parent will be inherited by the child.

 

Similarly, the keywords private or protected could have been used to inherit to those levels.

 

The Child class inherits the Parent's methods as follows:

Access by: public private protected
Same class Yes Yes Yes
Derived class Yes Yes No
External class Yes No No

 

Public inheritance provides the Parent's Public and Protected methods to the Child (as its own Public and Protected methods) but not its Private methods, these should be accessed using calls to the Parent's Public methods.

 

Protected inheritance provides the Parent's Public and Protected members as Protected members in the Child.

 

Private inheritance provides the Parent's Public and Protected members as Private members in the Child.

 

Here, the two subclasses (Ducati and Yamaha) use the Parent Motorbike class. This Parent class is never called directly, but just acts to provide similar qualities (members) to the derived classes, in this case the ability to set the int speed. The Child classes then use their own methods unique for their own behaviour, in this case their public methods of getSpeed and saySpeed (just using different names to show they are different):

#include <iostream>
using namespace std;

//define a Parent Abstract class - this is not called directly, but used by the derived classes
class Motorbike {
	protected:
		int speed; //declare an attribute to be inherited
	public:
		void setSpeed(int a) { //define the public interface
			speed = a ;
		}
};

class Ducati: public Motorbike { //define a Child Concrete class
	public:
		void getSpeed()
			{cout << "Here's a Ducati Panigale Motorbike going: " << speed << "mph..."<< endl ; }
};

class Yamaha: public Motorbike { //define a Child Concrete class
	public:
		void saySpeed()
			{cout << "This Yamaha Enduro Motorbike goes: " << speed << "mph, off road!" << endl ; }
};

int main() {
	Ducati panigale ; //declare an instance of the (ADT) class Ducati, called panigale
	panigale.setSpeed(129); //use the public method inherited from the Motorbike Parent Abstract class
	panigale.getSpeed(); //use the public method in the Child to display the previously set value

	Yamaha enduro ;
	enduro.setSpeed(79);
	enduro.saySpeed();

	return 0;
}

Compile & Run:

Here's a Ducati Panigale Motorbike going: 129mph...
This Yamaha Enduro Motorbike goes: 79mph, off road!

 

 

C++ also allows multiple inheritance, by providing a comma separated list of the classes to be inherited. This is an extremely simple example, just to show multiple classes being inherited on line 20:

#include <iostream>
#include <string>
using namespace std;

//define a Parent Abstract class - this is not called directly, but used by the derived classes
class Motorbike {
	protected:
		int speed; //declare an attribute to be inherited
	public:
		void setSpeed(int a) { //define the public interface
			speed = a ;
		}
};

class paint {
	protected:
		string colour ="red";
};

class Ducati: public Motorbike, protected paint {
	public:
		void getSpeed()
			{cout << "Here's a " << colour << " Ducati Panigale going: " << speed << "mph..."<< endl ; }
};

int main() {
	Ducati panigale ; //declare an instance of the (ADT) class Ducati, called panigale
	panigale.setSpeed(129); //use the public method inherited from the Motorbike Parent Abstract class
	panigale.getSpeed(); //use the public method in the Child to display the previously set value

	return 0;
}

Compile & Run:

Here's a red Ducati Panigale Motorbike going: 129mph...

Method Overloading

We have already seen a form of method overloading, whereby the constructor was overloaded with parameters to provide default values. This concept is simply applied to other methods of a class to provide method overloading, which is the same as function overloading but within a class.

 

Overload resolution is the term given to the process of determining the appropriate overloaded method, and relies upon the data type, order and quantity of parameters passed to the method.

 

The overloaded methods all have the same name but will have different signatures, as per their specific functionality.

 

For instance, consider three add() methods:

 

add(int, int) {...code body...}  //requires two int parameters

 

add(float, float) {...code body...}  //requires two float parameters

 

add(string, string) {...code body...}  //requires two string parameters

 

The compiler will select the correct method to be acted upon, according to the data type of parameters sent.

 

This very simple example shows a method being overloaded according to the data being sent to it:

#include <iostream>
#include <string>
#include <typeinfo>
using namespace std;

class classA {
public:
	void display(int myVar){cout << "Passed in value is: " << myVar << endl ;}
	void display(double myVar){cout << "Passed in value is: " << myVar << endl ;}
	void display(string myVar){cout << "Passed in value is: " << myVar << endl ;}
} ;

int main() {
	classA helga ;
	helga.display(5) ;
	helga.display(17.3634) ;
	helga.display("Amazing") ;

	return 0 ;
}

Compile & Run:

Passed in value is: 5
Passed in value is: 17.3634
Passed in value is: Amazing

 

 

 

A variation, showing the constructor being overloaded:

#include <string>
#include <iostream>
using namespace std;

class Rider {
private:
	int age, wins ;
	string name, bike, nationality ;

public:
	Rider ( ) ;                 //default constructor
	Rider ( string ) ;          //overloaded constructor
	Rider ( string, string ) ;  //overloaded constructor
	Rider ( string, int, string, int, string ) ;  //overloaded constructor
	~Rider() ; //destructor

	string getName() { return name; }
	int getAge() { return age; }
	string getBike() { return bike ; }
	int getChampionships() { return wins; }
	string getNationality() { return nationality; }
} ;

Rider::Rider ( ) {                   //default constructor method definition
	name = "Derrick" ;
	age = 49 ;
	bike = "Ducati 900SS" ;
	wins = 0 ;
	nationality = "English" ;
}

Rider::Rider (string name) {         //overloaded constructor method definition
	this -> name = name ;
	age = 43 ;
	bike = "pillion" ;
	wins = 0 ;
	nationality = "American" ;
}

Rider::Rider (string name, string nationality) {  //overloaded constructor method definition
	this -> name = name ;
	age = 43 ;
	bike = "Ex World SuperBike" ;
	wins = 3 ;
	this -> nationality = nationality ;
}
//overloaded constructor method definition
Rider::Rider ( string name, int age, string bike, int wins, string nationality) {
	this -> name = name ;
	this -> age = age ;
	this -> bike = bike ;
	this -> wins = wins ;
	this -> nationality = nationality ;
}

Rider::~Rider () {  //destructor method definition
	cout << "The object with " << nationality << " nationality has been destroyed!" << endl ;
}

void display(Rider &rider){
	cout << rider.getName() << " is a "  << rider.getAge() << " year old " ;
	cout << rider.getNationality() << " " << rider.getBike() << " rider, who has won " ;
	cout << rider.getChampionships() << " world championships!" << endl ;
}

int main() {

	Rider derrick ;
	Rider *myPtr = &derrick ;
	display(*myPtr) ;

	Rider vanessa ( "Vanessa" ) ;
	myPtr = &vanessa ;
	display(*myPtr) ;

	Rider bayliss ( "Troy Bayliss", "Aussie") ;
	myPtr = &bayliss ;
	display(*myPtr) ;

	Rider rossi ( "Valentino Rossi", 33, "Moto GP", 9, "Italian") ;
	myPtr = &rossi ;
	display(*myPtr) ;

	return 0 ;
}

Compile & Run:

Derrick is a 49 year old English Ducati 900SS rider, who has won 0 world championships!
Vanessa is a 43 year old American pillion rider, who has won 0 world championships!
Troy Bayliss is a 43 year old Aussie Ex World SuperBike rider, who has won 3 world championships!
Valentino Rossi is a 33 year old Italian Moto GP rider, who has won 9 world championships!
The object with Italian nationality has been destroyed!
The object with Aussie nationality has been destroyed!
The object with American nationality has been destroyed!
The object with English nationality has been destroyed!

Operator Overloading

Allows operators to work on classes.

 

Just as methods can be overloaded, so too can most of the operators:

+ - * / % = ! < > & | ^ << >>
+= -= *= /= %= == != <= >= &= |= ^= <<= >>=
++ -- ->* -> ~ , () new delete && || [ ] new[ ] delete[ ]

 

The exceptions being:

Ternary ? : sizeof() Scope Resolution :: Dot member selector . Member pointer selector .*

 

There are two main types of operator overloading:

  • Unary
    • Acting upon a single object
  • Binary
    • Acting on more than one object

Operators are overloaded by use of the keyword operator followed by the operator sign to be overloaded e.g. (operator=) and its new functionality then defined in the code body.

 

We have already seen the assignment = operator being overloaded in the above article on Copy constructor and assignment overloading, and the same principle is applied to overload the other operators.

 

Syntax:

return_type operator sign ( parameters ) {

...code body...

}

 

It should be fairly straight forward to see that a + b, could easily be translated into operator+(a, b). Assuming there are overloaded methods for the + operator, the compiler knows which one to use based on the type of parameters being sent. i.e. two ints or two floats, or two objects.

 

This example creates two simple Book objects consisting of an ISBN number and a price. The + operator has been overloaded to add the price component of the two objects, based on the type of parameter which in this case is a Book object. The overloaded method operates on the first operand (internally the this self reference) and adds the second object (as the passed in parameter):

#include <iostream>
using namespace std;

class Book {

private:
	int isbn ;
	float price ;

public:
	Book(int num, float cost) {
		isbn = num ;
		price = cost ;
	}
	float operator+(Book &id){
		cout << "The address of this is: " << this << endl;
		cout << "The address of the passed in parameter is: " << &id << endl;

		cout << "The price component of this is: " << this->price << endl ;
		cout << "The price component of the current object : " << price << endl ;
		cout << "The price component of the passed in parameter is: " << id.price << endl ;

		return price + id.price ;
	}
} ;

int main() {

	Book CPP(123459876, 15.66);
	Book Perl(67891234, 21.32);

	cout << "The address of CPP is: " << &CPP << endl ;
	cout << "The address of Perl is: " << &Perl << endl ;

	cout << "The total cost of the two books = " << CPP + Perl << endl ;

	return 0 ;
}

Compile & Run:

The address of CPP is: 0x28ff08
The address of Perl is: 0x28ff00
The address of this is: 0x28ff08
The address of the passed in parameter is: 0x28ff00
The price value of this is: 15.66
The price value of this is: 15.66
The price value of the passed in parameter is: 21.32
The total cost of the two books = 36.98

 

This example is overly verbose to show the internals of the objects.

Note, that a reference has been used as the parameter to simplify memory allocation, by referring to the passed in parameter another copy of the passed in object is not required and hence the additional memory overhead associated with that copy is not required. The example would have worked just the same without using a reference, but give it a try and you will see a new address created for the temporary object being created from the passed in parameter.

 

Also note, the operator+ function could have been called like so: CPP.operator+(Perl) however it is far less awkward to simply write + to produce the same result. Again, try it out; replace the addition expression on line 35 with CPP.operator+(Perl) to achieve the same (but not so easily read) result.

 


 

It is also possible to make the +, -, & and * operators act as a unary or a binary operator, depending on context. This example show the - minus sign operator being overload in both unary (line 15, called from lines 39 and 40) and binary (line 22, called from line 45) contexts:

#include <iostream>
using namespace std;

class CoOrd {

private:
	int xPos, yPos, zPos;

public:
	CoOrd(int x, int y, int z) {
		xPos = x ;
		yPos = y ;
		zPos = z ;
	}
	CoOrd operator-(){ //acting as a unary operator
		xPos = -xPos ;
		yPos = -yPos ;
		zPos = -zPos ;
		return CoOrd(xPos, yPos, zPos) ;
	}

	CoOrd operator-(CoOrd & id) { //acting as a binary operator
		xPos = xPos - id.xPos ;
		yPos = yPos - id.yPos ;
		zPos = zPos - id.zPos ;
		return CoOrd(xPos, yPos, zPos) ;
	}

	void showValues(){
		cout << "xPos: " << xPos << ", yPos: " << yPos << ", zPos: " << zPos <<endl ;
	}
} ;

int main() {

	CoOrd Home (12, -34, -63);
	CoOrd Work (-17, 22, 42);

	Home = -Home ;
	Work = -Work ;

	Home.showValues() ;
	Work.showValues() ;

	CoOrd result = Home - Work ;

	result.showValues() ;

	return 0 ;
}

Compile & Run:

xPos: -12, yPos: 34, zPos: 63
xPos: 17, yPos: -22, zPos: -42
xPos: -29, yPos: 56, zPos: 105

Unary Operator Overloading

Refers to operator overloading that act on a single object.

 

NOT Address One's Comp Point deref Plus Increment Negation Decrement
! & ~ * + ++ - --

 

The ! Logical NOT operator is being overloaded here on line 11 to return a bool to the test condition on line 28:

#include <iostream>
using namespace std;

class Value {

private:
	int val;
public:
	Value (int something = 0) : val(something) { }

	bool operator ! () {
		if( val % 2 == 0) {
			return true;
		} else {
			return false ;
		}
	}
} ;

int main() {

	int a ;
	cout << "Please enter a number" << endl ;
	cin >> a ;

	Value myVal(a);

	if (!myVal){
		cout << a << " is an even number" ;
	} else {
		cout << a << " is an odd number" ;
	}

	return 0 ;
}

Compile & Run:

Please enter a number
56
56 is an even number

Binary Operator Overloading

Refers to overloading operators that act on more than one object.

 

In this example, the less than < operator is being overloaded to check the 'GetUp' object against the 'GoToWork' object. In this case, the 'GetUp' object is the calling object and the 'GoToWork' object is the paraemeter being used in the overloaded method:

#include <iostream>
using namespace std;

class times {

private:
	int hrs, mins;

public:
	times(int a, int b) {
		hrs = a ;
		mins = b ;
	}
	bool operator < (times myObj){

		if (hrs < myObj.hrs) {
			return true;
		}
		if (hrs == myObj.hrs && mins < myObj.mins) {
			return true;
		}
		return false ;
	}
} ;

int main() {

	int a, b ;
	cout << "Please enter the hour of your getting up time" << endl ;
	cin >> a ;
	cout << "and the minutes" << endl ;
	cin >> b ;

	times GetUp (a, b);
	times GoToWork (07, 03);

	if (GetUp < GoToWork){
		cout << "You get up before you go to work" << endl ;
	} else {
		cout << "Hmm, temporal shift! You seem to go to work before you get up!" << endl ;
	}

	return 0 ;
}

Compile & Run:

Please enter the hour of your getting up time
8
and the minutes
22
Hmm, temporal shift! You seem to go to work before you get up!

inline Functions

The use of the keyword inline placed before a function, instructs the compiler to replace all functions calls to the function with a copy of the function code.

 

 

This greatly increases speed since the compiler doesn't need to store the return address from the function call, thus saving time.

 

However, the down side to this is that this can greatly increase the code size, since each function call is now expanded with a copy of the whole function code.

 

Syntax:

inline functionName() {

...code body

}

 

#include <iostream>
using namespace std;

inline float cube(int myVar){
	return myVar * myVar * myVar ;
}

int main() {

	float box1 = 3;

	cout << "The cube of box 1 is: " << cube(box1) << endl ;

	//the compiled equivalent code would be:
	cout << "The cube of box 1 is: " << box1 * box1 * box1 << endl ;

	return 0 ;
}

Compile & Run:

The cube of box 1 is: 27
The cube of box 1 is: 27

Operator Methods

aka Operator member functions

 

To make a class's code more readable, it is suggested that its methods be declared as prototypes within the class and that their implementation is external to the class.

 

Methods then gain access using the scope resolution operator :: e.g. void myClass::getValue(){return myVal;}  This effectively makes the external member a class method, but is it simply being defined externally, for readability.

 

Other functions can also gain access to a class's members by declaring their prototype as a friend within the class.

 

This goes the same for operator methods, which in general can be coded in this manner. However, some operators can only be defined as being class members, that is belonging to the class using the scope resolution operator ::, since they require an lvalue as the first operand, those being:

 

=, [], (), ->

 

new, delete, new[], delete[]
It has also been suggested that it is better to declare methods as friends, when the operator does not modify the operands, as it tries to make the code more readable by using explicitly listed parameters (as opposed to implicit or by the use of the this self reference pointer object).

 

  • If a unary operator is overloaded using a member function, it takes no arguments. If it is overloaded using a global function, it takes one argument.
  • If a binary operator is overloaded using a member function, it takes one argument. If it is overloaded using a global function, it takes two arguments.

Another aspect of declaring a method with class scope that is that it utilises the (generally considered hidden) this self referencing pointer object. Thus, the following two code snippets achieve similar functionality but use different techniques to do so:

 

using member function using friend function
#include <iostream>
using namespace std; 

 

 

class Speed {

private:

int turbo ;

public:

Speed(int mph = 165) : turbo(mph) { }

Speed operator+(int) ;

int getVal(){return turbo;}

} ;

 

//member function, using scope resolution ::
Speed Speed::operator +(int a) {

return Speed(this->turbo + a);

}

 

int main() {

Speed a ;
Speed b = a + 25 ;

cout << "Turbo speed: " << b.getVal() << endl ;

return 0 ;

}

#include <iostream> 

using namespace std;

 

 

class Speed {

private:

int turbo ;

public:

Speed(int mph = 165) : turbo(mph) { }

friend Speed operator+(const Speed &, int);

int getVal(){return turbo;}

} ;

 

//friend function, NOT a member function
Speed operator+(const Speed &faster, int a) {

return Speed(faster.turbo + a) ;

}

 

int main() {

Speed a ;
Speed b = a + 25 ;

cout << "Turbo speed: " << b.getVal() << endl ;

return 0 ;

}

Compile & Run:

Turbo speed: 190

 

Notice how the member function only requires one parameter since it is being self referenced using the this pointer. Whereas, the friend function requires both the calling object and the integer value as its parameters.

Overloading ++ & -- operators

There are two version of the increment and decrement operators, those being prefix and postfix:

 

  • increment
    • prefix: increment the value, then use it
      • ++a
    • postfix: use the value, then increment it
      • a++
  • decrement
    • prefix: increment the value, then use it
      • --a
    • postfix: use the value, then increment it
      • a--

Since there are two types of the same operator (pre/post fix) C++ introduced a hack to enable the compiler to determine which version is required.

 

The prefix versions take no parameters.

 

The postfix versions take a dummy int that is not used.

 

Their prototypes are typically as follows:

  • MyClass & operator++() ;  //prefix
  • const MyClass & operator++(int) ;  //postfix
  • MyClass & operator--() ;  //prefix
  • const MyClass & operator--(int) ;  //postfix

 

We can tell the difference between the prefix and the postfix, since the postfix operators have a dummy int in their signatures. Since the dummy is not  used or given a name, the compiler knows to treat this as a placeholder and therefore not bother with warnings that it has been declared but not used.

 

Both forms of the methods perform the same functionality and we can see that the postfix versions actually call the prefix versions to carry out the actual increment/decrement, but the only difference is in the return values. Specifically, the postfix versions return the original value that has been temporarily stored whilst the prefix inc/dec method is called to actually carry out the inc/dec and the original value is then returned, thus performing a postfix inc/dec.

 

In the example below, lines 3 and 7 of the output show that the prefix versions of the overloaded operators are called:

#include <iostream>
using namespace std;

class MyClass {
	private:
		int myVar ;

	public:
		MyClass(int val = 0) : myVar(val) {} //constructor

		MyClass & operator++() ;  //prefix
		MyClass & operator--() ;  //prefix

		const MyClass & operator++(int) ;  //postfix
		const MyClass & operator--(int) ;  //postfix

		int getVal(){return myVar;}
} ;

MyClass& MyClass::operator++() {  //prefix
	cout << "increment before PREfix " << myVar ;
		++myVar ;
	cout << ", increment after PREfix " << myVar << endl ;
		return *this ;
}
MyClass & MyClass::operator--() {  //prefix
	cout << "decrement before PREfix " << myVar ;
		--myVar ;
	cout << ", decrement after PREfix " << myVar << endl ;
		return *this ;
}
const MyClass & MyClass::operator++(int) {  //postfix
	cout << "increment before POSTfix " << myVar << endl ;
	MyClass temp(myVar) ;
	++*this;  //call the prefix version
	cout << "increment after POSTfix " << myVar << endl ;
	return temp;
}
const MyClass & MyClass::operator--(int) {  //postfix
	cout << "increment before POSTfix " << myVar << endl ;
	MyClass temp(myVar) ;
	--*this;  //call the prefix version
	cout << "increment after POSTfix " << myVar << endl ;
	return temp;
}

int main() {

	MyClass a(6);

	++a;
	a++;
	--a;
	a--;

	return 0 ;
}

Compile & Run:

increment before PREfix 6, increment after PREfix 7
increment before POSTfix 7
increment before PREfix 7, increment after PREfix 8
increment after POSTfix 8
decrement before PREfix 8, decrement after PREfix 7
increment before POSTfix 7
decrement before PREfix 7, decrement after PREfix 6
increment after POSTfix 6

<< & >> stream operator overloading

To read and write classes to and from streams (input / output), overloaded methods are required for the stream insertion << and stream extraction >> operators.

 

The << and >> are usually shit left and shift right bitwise operators that are overloaded in the ostream and istream classes of the iostream file, that is usually included at the beginning of a program with the following statement:    #include <iostream>

 

When used for output, the << operator is known as the insertion operator.

When used for input, the >> operator is known as the extraction operator.

 

These operators act as insertion / extraction operators when cout / cin is used on the left of the statement as the driving object for their relative implementations within the ostream or istream class of the iostream file.

 

Overloading << Output

cout is actually an object of the ostream class, which has function prototypes for the builtin data types such as int, float as follows:

 

ostream & operator<<(ostream&, int) ;

ostream & operator<<(ostream&, float) ;

 

As you can see they both return a reference to ostream, which is implemented in the function body by returning the next address for output to allow chained statements of the form: cout << objectA << objectB << objectC << endl ; The left operand should always be returned when we want overloaded binary operator to be chained.

 

They both also accept two parameters, those being a reference to ostream and the relevant data type.

 

Similarly, we can write our own overloaded << operator functions to act upon our classes.

 

A typical overloaded << function prototype would look like this:

 

friend ostream& operator<<(ostream&, const MyClass&) ;

 

The prototype is declared as a friend function to allow the function to access members of the class, and since it takes an ostream as its first parameter it can't be a member of the class.

 

The second parameter is defined as a const to ensure that the function does not alter the incoming object.

 

Because the function has to be defined as a friend and not a member function, the class name and scope resolution operator :: are not required. However, this necessitates the use of the class name and the dot operator for the relevant members required. This also means that the function cannot use the self referencing 'this' pointer, and cannot directly refer member data just by using their attribute name.

 

This example illustrates overloading the insertion stream operator >> to print the values of a class:

#include <string>
#include <iostream>
using namespace std;

class MyClass {
	private:
		int myVar ;
		float myFlt ;
		string myStr ;

	public:
		MyClass(int a = 6, float b = 6.6, string c = "Diablo") :
				myVar(a), 	myFlt(b), 		myStr(c) {}

	friend ostream& operator<<(ostream&, const MyClass&);
};

ostream& operator<<(ostream& abc, const MyClass& printClass){

	abc << "myVar: " << printClass.myVar << ", myFlt: " << \
			printClass.myFlt << ", myStr: " << printClass.myStr << endl ;
	return abc ;
}

int main() {

	MyClass david(9, 4.567, "David");

	cout << david ;

	MyClass noko ;

	cout << noko ;

	return 0 ;
}

Compile & Run:

myVar: 9, myFlt: 4.567, myStr: David
myVar: 6, myFlt: 6.6, myStr: Diablo

 


 

Overloading >> Input

 

The extraction operator >>  can also be overloaded for input, using the istream class of the iostream file, and has a similar prototype to the above:

 

friend istream& operator>>(istream&, MyClass&) ; 

 

Again the prototype is declared as a friend to allow access to  members of the class (in this case to set the values) and taking an istream as its first parameter means it can't be a member of the class.

 

Returning a reference allows chaining.

 

#include <string>
#include <iostream>
using namespace std;

class MyClass {
	private:
		int myVar ;
		float myFlt ;
		string myStr ;

	public:
		MyClass(int a = 6, float b = 6.6, string c = "Diablo") :
				myVar(a), 	myFlt(b), 		myStr(c) {}

	friend ostream& operator<<(ostream&, const MyClass&);

	friend istream& operator>>(istream&, MyClass&) ;
};

ostream& operator<<(ostream& abc, const MyClass& printClass){

	abc << "myVar: " << printClass.myVar << ", myFlt: " << \
			printClass.myFlt << ", myStr: " << printClass.myStr << endl ;
	return abc ;
}

istream& operator>>(istream& xyz, MyClass& getClass){
	cout << "please enter an int: " ;
	xyz >> getClass.myVar ;

	cout << "please enter a float: " ;
	xyz >> getClass.myFlt ;

	cout << "please enter a string: " ;
	xyz >> getClass.myStr ;

	return xyz ;
}

int main() {

	MyClass jimmy ;

	cin >> jimmy ;

	cout << jimmy ;

	return 0 ;
}
please enter an int: 5
please enter a float: 21.45
please enter a string: Jimmy
myVar: 5, myFlt: 21.45, myStr: Jimmy

Child Initialisation

A child class inherits members from its parent which it needs to initialise, as per any other class.

 

The preferred style of setting the attributes is to use an initialisation list, as can be seen in the parent class on line 11 in the example below.

 

The child also utilises an initialisation list, as per line 25. In this case, the first four attributes of the child object are of the same (inherited) type as the parent and therefore the parent class is utilised to initialise these values. This can be seen in the first parameter of the initialisation list: Motorbike(a,b,c,d), the remaining attributes for the child class are then initialised in the usual manner:

#include <iostream>
#include <string>
using namespace std;
class Motorbike {
	protected:
		string make, model ;
		int speed, cc ;
	public:
		//constructor
		Motorbike(string a, string b, int c, int d) :
				  make(a), model(b), speed(c), cc(d) {}  //initialisation list

	void showMotorbike() {
			cout << "Make: " << make << ", Model: " << model << \
			", Speed: " << speed << "mph, cc: " << cc << endl ;
		}
};

class Sports : public Motorbike {
	private:
		string track;
		int position ;
	public:
		//constructor
		Sports(string a, string b, int c, int d, string e, int f) :
						Motorbike(a,b,c,d), track(e), position(f) {}  //initialisation list

	void showSports() {
		cout << "Make: " << make << ", Model: " << model << \
		", Speed: " << speed << "mph, cc: " << cc << ", Track: " << \
		track << ", Position: " << position << endl ;
	}
};

int main() {

	Motorbike street("Honda", "Fireblade", 190, 1000) ;
	Sports racer("Ducati", "Panigale", 185, 1198, "Silverstone", 2) ;

	street.showMotorbike() ;

	racer.showSports() ;

	return 0;
}

Compile & Run:

Make: Honda, Model: Fireblade, Speed: 190mph, cc: 1000
Make: Ducati, Model: Panigale, Speed: 185mph, cc: 1198, Track: Silverstone, Position: 2

Multiple Files

Breaking programs down into multiple files enables the principle of "doing one task and doing it well" (aka the Unix philosophy).

 

This principle focuses on the creation of developing smaller, modular and more manageable code with well defined interfaces. Any issues local to that code should then be more easily addressed, whilst also enabling replacement/upgrade for newer versions to provide new features, and thus saving time/effort debugging problems on long/complex/unreadable code.

 

Modularity also allows specialisms to be utilised by more than one program (the 'why re-invent' rule) as can be seen in the STL.

 

Compilation times are improved by only having to compile files that have changed. Imagine if a program consisted of a single file with thousands of lines and took say 15 minutes to compile, only for you to remember a simple change and then have to recompile everything again.

 

The main program file has a forward declared prototype:

#include <iostream>

int multiply(int, int) ;

int main(){
	int x, y ;
	cout << "Please enter 2 numbers to multiply:" << endl ;
	cin >> x >> y ;
	cout << "The product of " << x << " x " << y << " = " << multiply(x,y) << endl ;
	return 0 ;
}

 

Implementation of the forward declared prototype in a separate file:

int multiply(int a, int b){
	return a * b ;
}

 

When compiled in an IDE, it should automatically find and compile both files for the program.

 

To compile from the command line, simply include the names of the files to be compiled with the desired output file name as follows:

 

g++ main.cpp multiply.cpp -o multiplier

 

*remember to add a .exe extension for windows environments.

Header Files

  • Contain declarations to be used in .cpp source code files
  • end with a .h extension
  • aka include file
  • Consist of:
    • Include (/ Header) Guard, which consists of conditional compilation directives:
      • #ifndef header_name
      • #define header_name
      • ... code definitions ...
      • #endif
  • Code Body

 

Example header file:

#ifndef MULTIPLY
#define MULTIPLY
int multiply(int, int);  //forward declaration
#endif

 

To use a header file, it has to be included by the preprocessor keyword #include followed by the name of the required header file:

#include <iostream>

#include "myHeader.h"

 

Angle brackets indicate files that come with the compiler/OS, whereas self created header files are included by stating their file name within double quotes.

 

Best practice

  • Use UPPERCASE for the header identifier
  • Always use include guards, to ensure the compiler doesn't try to duplicate a header file that has already been included in another file
  • Generally should only be used for declarations. Leave the implementations in .cpp files
  • Make them as specific as possible, just concentrating on one aspect

 

Compilation process:

  1. The preprocessor strips out all comments and replaces all code specified by the #include with copies of the actual code within those files
    1. The output is in the form of preprocessed source code, which is sent to the standard output
    2. The following command stops after the preprocessing stage and does not run the compiler proper:
      1. g++ -E main.cpp
  2. The compiler converts the source code to assembler code
    1. The following command stops after the compilation stage:
      1. g++ -S main.cpp
      2. creates a file with a .s extension containing assembly code
  3. The assembler converts the assembly code into object code
    1. The following command stops after the assembler stage:
      1. g++ -c main.cpp
      2. creates a file with a .o extension containing object code
  4. The linker converts all object and required linked files to produce an executable / binary file

 

The following command takes two .cpp files, and an included header file (specified via its #include statement):

g++ main.cpp multiply.cpp -o multiply

 

 

 

Composition

The concept of building (more complex) objects from other objects is known as composition.

 

Smaller specialised objects are created that perform one specific task and are then used as members of the complex object.

 

Composition creates objects that have the 'Has-A' relationship, such as a motorbike has an engine, or a plane has a wing, or a pub has a bar.

 

Composition also implies ownership, since when the complex object is created so are the subclasses, and accordingly die when the complex object dies.

 

 

Here we have an (RaceTeam) object called owner that uses another (Championship) object to keep track of the points accumulated:

#include <iostream>
#include <string>
using namespace std;
class Championship {
	private:
		int points;
	public:
		Championship() : points(0) {}
		Championship(int a) : points(a) {}
		//overload operator<< for object
		friend ostream& operator<<(ostream& out, const Championship &pts) {
			out << pts.getChampionship() ;
			return out;
		}
		void setChampionship(int b) { points += b; }
		int getChampionship() const {return points;}
};
class RaceTeam {
	private:
		string team;
		Championship total;
	public:
		RaceTeam() {}
		RaceTeam(string teamName, const Championship &here) : team(teamName), total(here) {}
		//overload operator<< for object
		friend ostream& operator<<(ostream& out, const RaceTeam &owner) {
			out << owner.team << " has " << owner.total << " points." ;
			return out;
		}
		void goChampionship(int c) { total.setChampionship(c); }
};
int main() {
	string name;
	int myPoints=0;

	cout << "What team do you ride for?" << endl ;
	cin >> name;

	RaceTeam owner(name, Championship(0));

	do {
		cout << owner << endl;
		cout << "How many points did you get in the last race (-1 to exit) : " << endl ;
		cin >> myPoints;
		owner.goChampionship(myPoints);
		} while (myPoints != -1) ;
	cout << "Thanks!" << endl ;
	return 0;
}

Compile & Run:

What team do you ride for?
Honda
Honda has 0 points.
How many points did you get in the last race (-1 to exit) :
20
Honda has 20 points.
How many points did you get in the last race (-1 to exit) :
25
Honda has 45 points.
How many points did you get in the last race (-1 to exit) :
25
Honda has 70 points.
How many points did you get in the last race (-1 to exit) : 
-1
Thanks!

Aggregation

Type of composition where no ownership is implied.

 

Thus (sub) objects are independent of each other, and are not destroyed when the aggregate object is destroyed.

 

Aggregate objects contain pointers or references to other objects, created outside of the class. When the aggregate object is destroyed and their pointers or references to the sub-objects goes out of scope, the sub-objects continue to exist.

 

Composition is creating a Person object, that gets destroyed when composition object goes out of scope

 

Whereas aggregation is creating a Person object that still exists when aggregation goes out of scope.

 

In this example, a sub-object is created (on line 34) independently of any other class. It is then used (line 38) in the constructor parameter when creating the aggregate object. When the aggregate object goes out of scope (line 39), the sub-object continues to exist as can be seen on line 40, until it is deleted on line 42:

#include<iostream>
#include <string>
using namespace std ;

class Person {
	private:
		string name ;
	public:
		Person(string myStr): name(myStr) {
			cout << "Person Created: " << myStr << endl ;
		};
		~Person(){
			cout << "Person Destroyed: " << name << endl ;
		};
	string getName(){ return name ;}
};

//Aggregation. Sub-objects live on when parent object destroyed
class MegaCorp{
	private:
		Person* investBanker;
	public:
		MegaCorp(Person* overvPaid){
			investBanker = overvPaid;
			cout << overvPaid->getName() << " got a job with MegaCorp!" << endl ;
		}
		~MegaCorp(){
			cout << investBanker->getName() << " got the sack from MegaCorp!!!" << endl ;
			investBanker = NULL;
		}
};

int main(){
	Person* female = new Person("Sharon") ;

	cout << female->getName() << " Lives!" << endl ;
	{
		MegaCorp tempJob(female) ;
	}
	cout << female->getName() << " Lives!" << endl ;

	delete female ;

	return 0 ;
}

Compile & Run:

Person Created: Sharon
Sharon Lives!
Sharon got a job with MegaCorp!
Sharon got the sack from MegaCorp!!!
Sharon Lives!
Person Destroyed: Sharon

Function Pointers

Allow functions to be assigned (and therefore reassigned) to pointers, assuming the signature following the pointer is the same.

 

int (*funcPtr) (int) ;

 

In the above example, funcPtr is a function pointer that returns an int, takes an int parameter and can point to any function that matches this signature.

 

The pointer should be enclosed within parentheses to ensure it is recognised as a pointer, due to the rules of operator precedence whereby parentheses are of higher precedence than *.

 

int (*funcPtr)() = funcOne ;  //declares a pointer that points to funcOne - Notice NO parentheses being used on the function being pointed to!

 

In this case, funcPtr now points to the funcOne function and can be reassigned to point to another function, as long as it uses the same signature.

 

In this example a function pointer has been created that points to functions that take one parameter in their signature:

#include <iostream>
using namespace std;

#define MODIFIER 7

int funcOne(int myVar){

	int x = myVar / MODIFIER  ;

	return x ;
}

int funcTwo(int myVar){

	int x = myVar * MODIFIER  ;

	return x ;
}

int main() {

	int (*funcPtr)(int) ;  //create an int function pointer

	funcPtr = funcOne ;  //assign it to a function

	cout << funcPtr(63) << endl ; //use function pointer

	funcPtr = funcTwo ;  //reassign to another function

	cout << funcPtr(5) << endl ; //use function pointer

    return 0;
}

Compile & Run:

9
35

Constant Pointers & Pointers to Constants

The use of the keyword const when working with pointers is the same as when it's used at any other time: you don't want the value to change! It is that simple. It was named const for a reason.

 

We (should) already know that a pointer is assigned the address of the target using the & address of operator:

 

int myInt = 42 ;

int *myPtr = &myInt ;

 

So, by making specific expressions const in the statement, we are stating that we do not want these values to change.

 

Here's three examples:

 

The first declares a constant int pointer. Thus stating that the value being pointed at cannot be changed:

const int *myPtr = &myInt ;

 

The second declares a constant pointer to an int. Thus the location stored on the pointer cannot change the address of what is being pointed at:

int * const myPtr = &myInt ;

 

Finally, this example declares a pointer to an int where both the value being pointed to and its address cannot change:

const int * const myPtr = &myInt ;

 

 

Basically, the placement of the keyword const determines what is being declared as a constant, the value, the address or both!

 


 

Const correctness, makes it clear that you do not want an object to change.

 

Good idea to create reference parameters const:

 

return_datatype funcName(const myClass& objectA){

...code body...

}

Dynamic Memory

When programs are created with variables of a pre determined / fixed size, the overall program has a fixed size and uses a specific amount of memory at run time.

 

However when variable sizes are unknown, say for multiple objects being created or for user input (e.g. text form entry), it is not possible to pre-determine the memory requirements as, for example, we will not know how many objects will be created or how many characters a user may enter into a text form. We therefore need a method to create (and delete) memory 'on the fly', at run time.

 

The technique to allocate new storage at run time is known as Dynamic Memory management, and utilises the operator keywords, new and delete to allocate and deallocate storage, as required.

 

It is very important to ensure that storage is deallocated using the operator keyword delete, in order to stop memory leaks!

 

Storage is dynamically allocated using the operator keyword new followed by the data type, which returns a (void) pointer to address space on the heap.

 

For example, int *ptr = new int  ; declares an int pointer, which will be assigned the address returned by new at runtime. We can then assign values to the dereferenced *ptr as follows: *ptr = 555 ;  //just as an example.

 

Once used, we delete the storage space using the operator keyword delete as follows: delete ptr ;

 

#include <iostream>
using namespace std;

int main() {

	int *myPtr ;  //just showing two techniques to assign new to  pointer
	myPtr = new int ;

	int *ptr = new int ;  //and here's the second version

	cout << "Please enter an integer: " << endl ;
	cin >> *myPtr ;

	cout << "Please enter another integer: " << endl ;
	cin >> *ptr ;

	cout << *myPtr << endl ;
	cout << myPtr << endl ;

	cout << *ptr << endl ;
	cout << ptr << endl ;

	delete myPtr ;

	delete ptr ;

	return 0;
}

Compile & Run:

Please enter an integer:
63
Please enter another integer:
49
63
0x3f1130
49
0x3f1140

 


 

 

Similarly, the operator keywords new[ ] and delete[ ] are used to allocate/deallocate storage for arrays:

#include <iostream>
using namespace std;

class myClass {

	public:
		myClass() {
			cout << "Constructor called!" << endl;
		}
		~myClass() {
			cout << "Destructor called!" << endl;
		}
};

int main() {

	myClass *myObject = new myClass[3];  //remembering new returns a pointer

	cout << myObject << endl ;  //just showing this is the same as first element 0

	for (int i = 0; i < 3; i++) {
		cout << &myObject[i] << endl ;
	}

	delete[] myObject ;

	return 0;
}

Compile & Run:

Constructor called!
Constructor called!
Constructor called!
0x711134
0x711134
0x711135
0x711136
Destructor called!
Destructor called!
Destructor called!

Inheritance construction & destruction order

A Child class derived from a Parent can be thought of as consisting of two parts: a Parent from which it inherits the Parent's members and its own Child part which has its own members.

 

The inherited Parent of a Child class is constructed before the Child.

 

Thus multiple inherited classes have the most senior class constructed before its child classes are constructed and so on down the chain of inheritance.

 

Similarly, a class's destructors destroy each part of a Child's derived classes, starting from the Child's own top level before destroying its inherited parts.

 

For example, a Child that is derived from a Parent that is derived from a Grand-Parent, will firstly have the Grand-Parent constructed, then the Parent before finally constructing the Child. Destruction starts with the Child, then the Parent and finally the Grand-Parent:

#include<iostream>
#include <string>
using namespace std ;

class First{
	public :
	First(){
		cout << "Constructing 1" << endl ;
	}
	~First(){
		cout << "Destroying 1" << endl << endl ;
	}
};

class Second : public First {
	public :
	Second(){
		cout << "Constructing 2" << endl ;
	}
	~Second(){
		cout << "Destroying 2" << endl ;
	}
};

class Third : public Second {
	public :
	Third(){
		cout << "Constructing 3" << endl << endl ;
	}
	~Third(){
		cout << "Destroying 3" << endl ;
	}
};

int main(){

	cout << "Instantiating one:" << endl ;
	First one ;

	cout << endl << "Instantiating two:" << endl ;
	Second two ;

	cout << endl << "Instantiating three" << endl ;
	Third three ;

	return 0 ;
}

Compile & Run:

Instantiating one:
Constructing 1 

 

Instantiating two:
Constructing 1
Constructing 2

 

Instantiating three
Constructing 1
Constructing 2
Constructing 3

 

Destroying 3
Destroying 2
Destroying 1

 

Destroying 2
Destroying 1

 

Destroying 1

Inherited members

A Child class inherits members of the Parent according to the Access Specifiers: public, private, protected

 

The Child can also call the constructor of the Parent in its initialisation list, as per line 3 (any required values should be included within the parentheses - if the Parent constructor requires these values):

class Child : public Parent {
	public :
		Child() : Parent()
		{
		}
} ;

 

Similarly, the Child inherits the attribute identifiers, but not the values, of the Parent and can then utilise its own methods upon these attributes, say, to set its own values for these inherited fields.

 

This example shows the Child utilising the Parent's protected string name, which it then uses within its own method on line 26, which can be called as per line 37:

#include<iostream>
#include <string>
using namespace std ;

class Parent {
	protected :
		string name ;
	public :
		Parent(string parentName) : name(parentName) {
			cout << "Constructing Parent attributes for " << parentName << endl ;
		}
		~Parent(){
			cout << "Destroying Parent attributes for " << name << endl << endl ;
		}
};

class Child : public Parent {
	public :
		Child(string childName) : Parent(childName) {
			cout << "Constructing Child attributes for " << childName << endl ;
		}
		~Child(){
			cout << "Destroying Child attributes for " << name << endl ;
		}

		string getName(){return name ;}
};

int main(){

	cout << "Instantiating Parent" << endl ;
	Parent parentObject("Bill") ;

	cout << endl << "Instantiating Child" << endl ;
	Child childObject("Ted") ;

	cout <<	childObject.getName() << " had its Parent's attributes constructed before its own" << endl ;

	cout << endl << "Objects will now be destroyed" << endl ;
	return 0 ;
}

Compile & Run:

Instantiating Parent
Constructing Parent attributes for Bill 

 

Instantiating Child
Constructing Parent attributes for Ted
Constructing Child attributes for Ted
Ted had its Parent's attributes constructed before its own

 

Objects will now be destroyed
Destroying Child attributes for Ted
Destroying Parent attributes for Ted

 

Destroying Parent attributes for Bill

 

 

Overriding

Allows inherited methods to be redefined.

 

Prototype of the Child must have the same signature as the Parent.

 

In this example the Parent and the Child have the same method theFunc() (on lines 12 and 22 respectively) which is being implemented differently in the Child, on line 23:

#include<iostream>
#include <string>
using namespace std ;

class Parent {
	protected :
		string name ;
	public :
		Parent(string parentName) : name(parentName) {
			cout << "Initialising " << parentName << "'s Parent attributes" << endl ;
		}
		void theFunc(){
			cout << "Using Parent method! " << name << " has " << name.length() << " letters" << endl ;
		}
};

class Child : public Parent {
	public :
		Child(string childName) : Parent(childName) {
			cout << "Initialising " << childName << "'s Child attributes" << endl ;
		}
		void theFunc(){
			cout << "Using Child method! " << "The are " << name.length() << " letters in " << name << endl ;
		}
};

int main(){

	Parent parentObject("Bill") ;
	parentObject.theFunc() ;
	cout << endl ;

	Child childObject("Ted") ;
	childObject.theFunc() ;

	return 0 ;
}

Compile & Run:

Initialising Bill's Parent attributes
Using Parent method! Bill has 4 letters 

 

Initialising Ted's Parent attributes
Initialising Ted's Child attributes
Using Child method! The are 3 letters in Ted

 

 

Alternatively, you can simply add to the Parent's method (called on line 23), and adding to, as per line 24:

#include<iostream>
#include <string>
using namespace std ;

class Parent {
	protected :
		string name ;
	public :
		Parent(string parentName) : name(parentName) {
			cout << "Initialising " << parentName << "'s Parent attributes" << endl ;
		}
		void theFunc(){
			cout << "Using Parent method! " << name << " has " << name.length() << " letters" << endl ;
		}
};

class Child : public Parent {
	public :
		Child(string childName) : Parent(childName) {
			cout << "Initialising " << childName << "'s Child attributes" << endl ;
		}
		void theFunc(){
			Parent::theFunc() ;
			cout << "Using Child method! " << "If you squared the number of letters in " \
					<< name << " you'd get " << name.length() * name.length() << " letters!" ;
		}
};

int main(){

	Parent parentObject("Bill") ;
	parentObject.theFunc() ;
	cout << endl ;

	Child childObject("Ted") ;
	childObject.theFunc() ;

	return 0 ;
}

Compile & Run:

Initialising Bill's Parent attributes
Using Parent method! Bill has 4 letters 

 

Initialising Ted's Parent attributes
Initialising Ted's Child attributes
Using Parent method! Ted has 3 letters
Using Child method! If you squared the number of letters in Ted you'd get 9 letters!

Virtual base class

When used in the declaration of a derived class, the keyword virtual instructs the compiler to only use the base class once regardless of the number of derived classes using it.

 

This overcomes the so called Diamond Problem, whereby the base class constructor is invoked for each derived class. This could potentially invoke many copies of the base class constructor and thus take up more memory resources than required.

 

Here we have an imaginary JamesBondSuperCar that is derived from three types of vehicle:

 

 

This example shows the problem:

#include <iostream>
#include <string>
using namespace std;

class Vehicle {
	public :
		Vehicle() {cout << "Vehicle constructor invoked" << endl ;}
	int speed ;
};

class Car : public Vehicle {
	public :
		Car(){cout << "\tCar constructor invoked" << endl ;}
};

class Submarine : public Vehicle {
	public :
		Submarine(){cout << "\tSubmarine constructor invoked" << endl ;}
};

class Jet : public Vehicle {
	public :
		Jet(){cout << "\tJet constructor invoked" << endl ;}
};

class JamesBondSuperCar : public Car, public Submarine, public Jet {
	public:
		JamesBondSuperCar(){cout << "\t\tJamesBondSuperCar constructor invoked" << endl ;}
};

int main() {

	JamesBondSuperCar miniCooper ;

	return 0;
}

Compile & Run:

Vehicle constructor invoked 

Car constructor invoked

Vehicle constructor invoked

Submarine constructor invoked

Vehicle constructor invoked

Jet constructor invoked

JamesBondSuperCar constructor invoked

 

As can be seen, the base constructor has been called three times and therefore three copies of its members are being stored. The problem is further compounded if you wanted to define the integer speed attribute, since the compiler doesn't know which speed to set and complains with an error stating that speed is ambiguous.

 

To overcome this, we use the keyword virtual for each declaration of the base class in the derived classes, as per lines 11, 16 and 21:

#include <iostream>
#include <string>
using namespace std;

class Vehicle {
	public :
		Vehicle() {cout << "Vehicle constructor invoked" << endl ;}
	int speed ;
};

class Car : virtual public Vehicle {
	public :
		Car(){cout << "\tCar constructor invoked" << endl ;}
};

class Submarine : virtual public Vehicle {
	public :
		Submarine(){cout << "\tSubmarine constructor invoked" << endl ;}
};

class Jet : virtual public Vehicle {
	public :
		Jet(){cout << "\tJet constructor invoked" << endl ;}
};

class JamesBondSuperCar : public Car, public Submarine, public Jet {
	public:
		JamesBondSuperCar(){cout << "\t\tJamesBondSuperCar constructor invoked" << endl ;}
};

int main() {

	JamesBondSuperCar miniCooper ;

	miniCooper.speed = 666 ;

	return 0;
}

Compile & Run:

Vehicle constructor invoked 

Car constructor invoked
Submarine constructor invoked
Jet constructor invoked

JamesBondSuperCar constructor invoked

 

 

Now we can see that the base constructor  has only been called once, and we can also set the speed attribute for the derived object.

Upcasting

Process of casting/referring to a derived class object as if it were a base class object. This is possible due to the "is-a" relationship between the base and derived classes, whereby the derived class inherits the public (and protected) members without an explicit type cast. This, in turn, is possible because the derived class object is a type of its base class object.

 

 

Referred to as Upcasting due to UML notation of derived class pointing up to the base class.

 

Achieved by using/converting the address of the derived class object to a base class object.

 

This example shows a DerivedClass object being sent to the function accepting BaseClass types:

#include <iostream>
#include <string>
using namespace std;

class BaseClass {
	public:
		void speak(){
			cout << "BaseClass speak function." << endl ;
		}
};

class DerivedClass : public BaseClass {
	public:
		void speak(){
			cout << "DerivedClass speak function." << endl ;
		}
};

void say(BaseClass & xyz){  //accepting BaseClass type
	xyz.speak() ;
}

int main() {
	DerivedClass myObject ;  //instantiate DerivedClass object

	say(myObject) ;  //send DerivedClass object to function accepting BaseClass type = upcasting

	return 0;
}

Compile & Run:

BaseClass speak function.

 

 

This example shows a DerivedClass object being assigned to a BaseClass pointer, which is then used to invoke the method:

#include <iostream>
#include <string>
using namespace std;

class BaseClass {
	public:
		void speak(){
			cout << "BaseClass speak function." << endl ;
		}
};

class DerivedClass : public BaseClass {
	public:
		void speak(){
			cout << "DerivedClass speak function." << endl ;
		}
};

int main() {

	DerivedClass myObject ;  //declare DerivedClass object
	BaseClass *myPtr = &myObject;  //create BaseClass type pointer and assign to address of DerivedClass object
	myPtr->speak();  //use the pointer to invoke the method

	BaseClass *otherPtr = new DerivedClass;  //create BaseClass type pointer and assign to new DerivedClass
	otherPtr->speak();  //use the pointer to invoke the method

	return 0;
}

Compile & Run:

BaseClass speak function.
BaseClass speak function.

 

 

Notice, the BaseClass method has been invoked! Whereas, ideally, we would like the DerivedClass method invoked.

 

This is a major problem and is due to the compiler only knowing that a BaseClass type object has been used when invoking the method, instead of doing what we would prefer it do, to invoke the DerivedClass method.

 

The solution to this is Polymorphism, or more specifically virtual functions.

Binding

In terms of OOP, Binding is the concept of how a call to a function is made to the function body.

 

All variables and functions have an address, and binding is the process used to convert their identifiers into an address.

 

There are two main types of binding:

  • Early / Static / Compile-Time Binding / Static Dispatch
    • Used when the compiler can associate the identifier with an address
    • Only option in C
  • Late / Dynamic / Run-Time Binding / Dynamic Dispatch
    • Used when the compiler doesn't know or have information to associate the identifier with an address
    • Implemented using virtual functions (with an object address of the Base class type)
    • akin to an 'abstract', placeholder

 

We are interested in late binding since it is this ability that allows our code to become polymorphic.

 

Late binding is implemented by the compiler when the keyword virtual is applied to a class's function. This informs the compiler not to implement early binding and instead implement the late binding mechanisms. It achieves this by creating a (hidden) VTABLE for each class that contains a virtual function, in which the addresses of the virtual functions are stored for that class. It then creates a (hidden) pointer VPTR for each class that points to the VTABLE for the object. When a virtual function call is made via a base class type, the compiler creates code to get the VPTR to lookup the correct (derived) function address in the VTABLE.

 

*Note: this is an overly simplified summary. For a more detailed explanation please read Polymorphism & Virtual Functions, and/or Smashing C++ VPTRS.

The Virtual Pointer & Virtual Table

When the compiler sees the keyword virtual in a class's statement, it automatically creates a virtual pointer and corresponding virtual table for each class (i.e. the base and all derived classes).

 

When a method of a derived object is invoked a lookup is made using its vpointer that points to its vtable for a corresponding address, if the class has its own method that address will be used, if not the address of the base (inherited) method will be used.

 

 

This example shows two derived classes with their own version of the base classes methods. Just for clarity, I have shown what happens when the derived classes methods are invoked, and also what happens when it hasn't overridden the base method (i.e. it falls back to the base method):

#include <iostream>
#include <string>
using namespace std;

class Animal {
	public:
		virtual void speak() {
			cout << "Base: Mumblings from the primordial soup. " << endl ;
		}
		virtual void walk() {
			cout << "Base: Bumping into things." << endl ;
		}
		virtual ~Animal(){}
};

class Cat : public Animal {
	public :
		void speak(){
			cout << "Derived: Meow." << endl ;
	}
		virtual ~Cat(){}
};

class Dog : public Animal {
	public :
		void walk(){
			cout << "Derived: Running after tail at 10mph" << endl ;
	}
		virtual ~Dog(){}
};

int main() {

	Animal cell ;  //create new instance of base class object
	Animal *ptr = &cell;  //create a base class (data type) pointer and assign the address of cell
	ptr->speak() ;  //invoke its speak method
	ptr->walk() ;  //invoke its walk method

	Cat silver ;  //declare a Cat object called silver, derived from Animal
	ptr = &silver ;  //assign the address of silver to the base class (data type) pointer
	ptr->speak() ;  //invoke the derived overridden speak method
	ptr->walk() ;  //invoke the inherited walk method

	Dog rex ;  //declare a Dog object called rex, derived from Animal
	ptr = &rex ;  //assign the address of rex to the base class (data type) pointer
	ptr->speak() ;  //invoke the inherited speak method
	ptr->walk() ;  //invoke the derived overridden walk method

	return 0;
}

Compile & Run:

Base: Mumblings from the primordial soup.
Base: Bumping into things.
Derived: Meow.
Base: Bumping into things.
Base: Mumblings from the primordial soup.
Derived: Running after tail at 10mph

 

 

We can see the base class object calls its own methods on lines 1 & 2 of the output. Line 3 shows the Cat derived object (silver) invoking its correct overridden method, but on line 4 has to fall back to the base method. Similarly, line 5 shows the Dog derived object (rex) falling back to the base method, but being able to invoke its correct overridden method on line 6.

Polymorphism

From Greek, meaning many (poly) forms (morph), provides the ability to assign different meanings or functionality to an object depending on its context.

 

Polymorphism is the process of taking a derived class object, assigning it to a base class type and invoking the correct (virtual) function according to the derived class type.

 

This allows an abstracted method of programming that focuses on generalised code, so that a programmer needs only to know about the interface and not its implementation. Allows: same interface, different implementation, by using / invoking the same method (behaviour) identifier (e.g. talk() which might result in: moo, meow, grr, hello) based on the object invoking the method.

 

Since derived classes are inherited from base classes, they are type compatible, i.e. a derived class is a form of a base class.  Therefore any derived class of a base class can invoke the methods inherited (and overridden) from the base class.

 

Polymorphism is realised when the base class methods are made virtual, which instructs the compiler to implement code that will select the correct derived class method at run time using late binding, instead of type of the pointer to determine which to call.

 

Polymorphism allows generalisation (abstraction) and since all the methods are inherited in the derived classes, it's not 'really' calling the base method name - it's simply calling the method name, which is selected by the derived object type. It's all about type!

 

Polymorphism allows the language to become object oriented, true it needs the other pillars (encapsulation and inheritance), but this is where OOP truly becomes possible since it enables abstraction, the ability to loosely say I want one of these to do this, e.g. animal, make sound

Virtual Methods

By using the keyword virtual in a method declaration we are able to implement Polymorphism (for derived objects of their base class type).

 

In the above section on upcasting we saw how the base class method was invoked for derived objects of a base class type. This problem is overcome by utilising virtual methods.

 

Simply by placing the keyword virtual against the base class method, we now get the desired behaviour :

#include <iostream>
#include <string>
using namespace std;

class BaseClass {
	public:
		virtual void speak(){
			cout << "BaseClass speak function." << endl ;
		}
};

class DerivedClass : public BaseClass {
	public:
		void speak(){
			cout << "DerivedClass speak function." << endl ;
		}
};

int main() {

    DerivedClass myObject ;  //declare DerivedClass object
    BaseClass *myPtr = &myObject;  //create BaseClass type pointer and assign to address of DerivedClass object
    myPtr->speak();  //use the pointer to invoke the method

    BaseClass *otherPtr = new DerivedClass;  //create BaseClass type pointer and assign to new DerivedClass
    otherPtr->speak();  //use the pointer to invoke the method

    return 0;
}

Compile & Run:

DerivedClass speak function.
DerivedClass speak function.

 

 

Only the base method declaration needs the keyword virtual (not the definition) and is then considered virtual in all derived classes, which (usually) override the method to provide the desired derived behaviour.

 

 

 

We are also still able to instantiate base class objects and call its method:

#include <iostream>
#include <string>
using namespace std;

class BaseClass {
	public:
		virtual void speak(){
			cout << "BaseClass speak function." << endl ;
		}
};

class DerivedClass : public BaseClass {
	public:
		void speak(){
			cout << "DerivedClass speak function." << endl ;
		}
};

int main() {

    DerivedClass myObject ;  //declare DerivedClass object
    BaseClass *myPtr = &myObject;  //create BaseClass type pointer and assign to address of DerivedClass object
    myPtr->speak();  //use the pointer to invoke the method

    BaseClass *otherPtr = new DerivedClass;  //create BaseClass type pointer and assign to new DerivedClass
    otherPtr->speak();  //use the pointer to invoke the method

    cout << endl ;
    BaseClass test;
    test.speak();

    return 0;
}

Compile & Run:

DerivedClass speak function.
DerivedClass speak function.

BaseClass speak function.

Extensibility

Extensibility refers to the concept of being able to add new (derived) classes, and hence behaviour, from a common base class.

 

Assuming the base class has a well defined interface, any new derived class can simply follow on using the same methods, implemented as required in the derived class and therefore extending the capabilities of the program.

 

Here's a derived class inheriting from a base class:

#include <iostream>
#include <string>
using namespace std;

class Animal {
	protected:
		int legs = 2 ;
	public:
		virtual void makeNoise() {
			cout << "Mumblings from the primordial soup. Hmm, I've just grown " << legs << " legs!!" << endl ;
		}
	virtual ~Animal(){}
};

class Dog : public Animal {
	public :
		void makeNoise(){
			cout << "Woof! Dirty dog with " << legs * 2 << " canine legs." << endl ;
	}
		virtual ~Dog(){}
};

int main() {
	Animal cell ;
	Animal *ptr = & cell; //declare a Base class pointer
	ptr->makeNoise() ;  //invoke the method

	Dog rex ;  //declare a Dog object called rex
	ptr = &rex ;  //assign the address of rex to the Base class pointer
	ptr->makeNoise() ;  //invoke the method

	return 0;
}

Compile & Run:

Mumblings from the primordial soup. Hmm, I've just grown 2 legs!!
Woof! Dirty dog with 4 canine legs.

 

 

 

We can simply add more derived classes and continue using the same base class interface:

#include <iostream>
#include <string>
using namespace std;

class Animal {
	protected:
		int legs = 2 ;
	public:
		virtual void makeNoise() {
			cout << "Mumblings from the primordial soup. Hmm, I've just grown " << legs << " legs!!" << endl ;
		}
	virtual ~Animal(){}
};

class Dog : public Animal {
	public :
		void makeNoise(){
			cout << "Woof! Dirty dog with " << legs * 2 << " canine legs." << endl ;
	}
		virtual ~Dog(){}
};

class Cat : public Animal {
	public :
		void makeNoise(){
			cout << "Meow. Cleaning my " << legs * 2 << " feline legs with claws!" << endl ;
	}
		virtual ~Cat(){}
};

class Spider : public Animal {
	public :
		void makeNoise(){
			cout << "Eeek. I've got " << legs * 4 << " spindly legs and venom!" << endl ;
	}
		virtual ~Spider(){}
};

int main() {
	Animal cell ;
	Animal *ptr = & cell; //declare a Base class pointer
	ptr->makeNoise() ;  //invoke the method

	Dog rex ;  //declare a Dog object called rex
	ptr = &rex ;  //assign the address of rex to the Base class pointer
	ptr->makeNoise() ;  //invoke the method

	Cat silver ;
	ptr = &silver ;
	ptr->makeNoise() ;

	Spider syd ;
	ptr = &syd ;
	ptr->makeNoise() ;

	return 0;
}

Compile & Run:

Mumblings from the primordial soup. Hmm, I've just grown 2 legs!!
Woof! Dirty dog with 4 canine legs.
Meow. Cleaning my 4 feline legs with claws!
Eeek. I've got 8 spindly legs and venom!

 

 

OK, these examples may seem overly simplified, but the idea is to focus upon just the one aspect in question - in this case extensibility

Virtual Destructors

When a derived object is instantiated and upcast to its base class data type, a problem arises when the derived object goes out of scope or is removed by way of the keyword delete, inasmuch as the compiler thinks that since it has been upcast it therefore uses the base destructor method, which can potentially leave copies of the derived objects untouched (consuming memory or leading to memory leaks).

 

Therefore the general rule is to always make destructors virtual when dealing with derived classes.

 

In this example a base class object is created, a base class data type pointer is assigned to the object and its method is invoked.  The base class data type pointer is then assigned to a new instance of the derived (Cat) class and its overridden method is invoked.  The (base class data type) pointer is then deleted. The result is that (behind the scenes, the derived object is still consuming memory): 

#include <iostream>
#include <string>
using namespace std;

class Animal {
	public:
		virtual void speak() {
			cout << "Base: I'm Alive." << endl ;
		}
//		virtual ~Animal(){cout << "Base: I'm dead."<<endl;}
};

class Cat : public Animal {
	public :
		void speak(){
			cout << "Derived: Meow." << endl ;
	}
//		virtual ~Cat(){cout << "Cat destroyer"<<endl;}
};

int main() {

	Animal feline ;
	Animal *ptr = &feline;
	ptr->speak() ;

	ptr = new Cat ;
	ptr->speak() ;

	delete ptr ;

	return 0;
}

Compile & Run:

Base: I'm Alive.
Derived: Meow.

 

 

Uncommenting line 10 provides a virtual destructor for the base class:

Compile & Run:

Base: I'm Alive.
Derived: Meow.
Base: I'm dead.
Base: I'm dead.

 

OK a little better, but the derived object still only has the base destructor acting upon it thereby leaving the derived portion potentially still consuming memory.

 

Now uncommenting line 18, we finally have the desired action whereby the derived object is fully destroyed:

Compile & Run:

Base: I'm Alive.
Derived: Meow.
Cat destroyer
Base: I'm dead.
Base: I'm dead.

 

Abstract Base Class

An abstract base class is a construct that is purely used to be inherited form, and cannot be instantiated on its own.

 

It is implemented by creating at least one pure virtual function within the class.  A pure virtual function is a special kind of virtual function that has no body and is assigned the value of 0.

 

The pure virtual function simply acts as a placeholder, that indicates that it is meant to be defined/overridden in a derived class.

 

Syntax:

virtual return_type function_identifier() = 0 ;

 

This forces derived class to define the pure virtual function, and if they don't they're also considered abstract classes (since they inherit their base class methods) and they too cannot be instantiated!

 

This technique ensures a specific design is followed, by forcing those pure virtual functions to be defined in the derived classes.

 

This example shows an abstract base class with its pure virtual functions on lines 10 and 11:

#include <iostream>
#include <string>
using namespace std;

class Vehicle {
	protected:
		int speed = 0 ;

	public:
		virtual void accelerate() = 0 ;
		virtual void brake() = 0 ;
		virtual ~Vehicle(){ }
};

class Bike : public Vehicle {
	public :
		void accelerate(){
			speed += 10 ;
			cout << "Bike speeding up to " << speed << " mph." << endl ;
	}
		void brake(){
			speed -= 10 ;
			cout << "Bike slowing down to " << speed << " mph." << endl ;
	}
		virtual ~Bike(){ }
};

int main() {

	Bike ducati ;
	Vehicle *ptr = &ducati;
	ptr->accelerate() ;
	ptr->accelerate() ;
	ptr->accelerate() ;
	ptr->brake() ;
	ptr->brake() ;

	return 0;
}

Compile & Run:

Bike speeding up to 10 mph.
Bike speeding up to 20 mph.
Bike speeding up to 30 mph.
Bike slowing down to 20 mph.
Bike slowing down to 10 mph.

 


 

As a (related) aside: An Interface Class is a type of class where all methods are pure virtual and it has no member variables! Therefore it cannot be instantiated and has no implementation, thus enforcing derived classes to define the functionality required. The benefit of this is that it provides the flexibility to derive specific implementations (e.g. email user or open an incident ticket) and enables extensibility to add new implementations that utilise the existing interface of the interface class.

:: Scope Resolution Operator

The Scope Resolution Operator consists of two colons :: and is used to specify which named context area, an identifier (variable, etc) is being referred to.

 

The named context area might be a function, class, struct, namespace.

 

For instance a class may have a number of members contained within its body code block, which can be referred to using the scope resolution operator, like so:

 

className :: member ;

 

If the className is omitted, it is assumed that the program's Global scope is being referred to, like so:

 

:: member ;

 

This example creates a global function, a namespace and a class all with unique returned values:

#include <iostream>
using namespace std;

int getValue(){
	return 21 ;
}

namespace mySpace {
	int getValue(){
		return 42 ;
	}
};

class myClass {
	public:
		static int getValue(){
			return 17 ;
		}
};

int main() {

	cout << "Global using :: to refer to getValue() = " << ::getValue() << endl ;
	cout << "Global no scope resolution, getValue() = " << getValue() << endl << endl ;

	cout << "Namespace referring to getValue() = " << mySpace::getValue() << endl << endl ;

	cout << "Now instantiating a myClass object" << endl ;

	myClass* ptr = new myClass() ;
	myClass myObject ;

	cout << "Class using -> to refer to getValue() = " << ptr->getValue() << endl ;
	cout << "Class using . member access to refer to getValue() = " << myObject.getValue() << endl ;
	cout << "Class using :: to refer to getValue() = " << myClass::getValue() << endl ;

	return 0;
}

Compile & Run:

Global using :: to refer to getValue() = 21
Global no scope resolution, getValue() = 21 

 

Namespace referring to getValue() = 42

 

Now instantiating a myClass object
Class using -> to refer to getValue() = 17
Class using . dot member access to refer to getValue() = 17
Class using :: to refer to getValue() = 17

Recursion

Process of a function calling itself.

 

Requires a test to get out of the loop - usually an if test.

 

Recursive Factorial example:

#include <iostream>
using namespace std;

int factorial (int i) {

	if (i <=1){return 1;}  //test, to get out of the recursive loop

	int t = i * factorial(i-1) ; //function calling itself

	cout << "Function calling itself on round " << i << ", the factorial is: " << t << endl ;

	return t ;
}

int main() {

	cout << "Please enter a number to calculate its factorial." << endl ;

	int i ;

	cin >> i ;

	cout << endl << "The final factorial of " << i << " is " << factorial(i) << endl ;

	return 0;
}

Compile & Run:

Please enter a number to calculate its factorial.
6
Function calling itself on round 2 , the factorial is:2
Function calling itself on round 3 , the factorial is:6
Function calling itself on round 4 , the factorial is:24
Function calling itself on round 5 , the factorial is:120
Function calling itself on round 6 , the factorial is:720 

 

The final factorial of 6 is 720

 

 

 

And for the famous Fibonacci series example:

#include <iostream>
using namespace std;

int Fibonacci(int i) {
	if (i == 0 || i == 1) {
		return i;
	}

	return Fibonacci(i - 1) + Fibonacci(i - 2);
}

int main() {

	cout << "Please enter the number of iterations for this Fibonacci series: " << endl ;

	int fibs ;
	cin >> fibs ;

	cout << "The Fibonacci series for " << fibs << " items is:" ;

	for (int i = 0; i < fibs; i++){

		cout << " "<< Fibonacci(i) ;

	}

	return 0;
}

Compile & Run:

Please enter the number of iterations for this Fibonacci series:
12
The Fibonacci series for 12 items is: 0 1 1 2 3 5 8 13 21 34 55 89

Preprocessor

Compiler instructions to be carried out before the actual compilation.

 

Always begins with a hash symbol #

 

Not terminated with a semi-colon ;

 

Used when compiling the program.

 

The most common preprocessor directives are: #include and #define

 

#define Defines a preprocessor macro
#include Substitues a preprocessor macro
#undef Undefines a macro
#ifdef Returns TRUE if macro is defined
#ifndef Returns TRUE if macro is not defined
#if Tests if a compile time condition is TRUE
#else the alternative for #if
#elif #else and #if in one statement
#endif Ends preprocessor conditional
#error Prints error message on stderr

 

 

Conditional Compilation can be carried out by utilising the various conditional preprocessor directives stated above. This example defines (or in this case, sets) a condition to be checked upon:

#include <iostream>
using namespace std;

#define DEBUG

int main () {

	#ifdef DEBUG
		cerr << "Debug message goes here." << endl ;
	#endif

	cout << "This simply shows what happens when the above DEBUG" << \
			" (not a keyword) is defined/set." << endl ;

	#ifdef DEBUG
		cerr << "Debug message goes here." << endl ;
	#endif

	cout << "Comment out the above #define preprocessor directive to 'turn off' " << \
			"debugging comments." << endl ;

	#ifdef DEBUG
		cerr << "Debug message goes here." << endl ;
	#endif

	return 0;
}
Debug message goes here.
Debug message goes here.
Debug message goes here.
This simply shows what happens when the above DEBUG (not a keyword) is defined/set.
Comment out the above #define preprocessor directive to 'turn off' debugging comments.

 

 

The # preprocessor operator can be used to convert specified text into a quoted string:

#include <iostream>
using namespace std;

#define STRINGTHIS( a ) #a  //note, using same argument after #

int main () {

	cout << STRINGTHIS(Hello World!) << endl ;

	return 0;
}
Hello World!

#include

#include instructs the compiler to merge the specified file before the program is converted into machine code

 

#include <iostream>

 

By including the iostream include file, access to its various functions become available to the program.

 

For example, functions to help reading and writing data, including cout for sending data to the screen and cin for receiving data from the keyboard.

#define

The #define preprocessor takes the form: #define identifier value

#include <iostream> //allows the use of the cout function in the iostream library file
using namespace std ;

#define PI 3.14
#define RADIUS 20

int main()
{
   int area ;

   area = PI * (RADIUS * RADIUS) ;
   cout << "A circle with a radius of " << RADIUS << "cm has an area of: " << area << "cm." << endl ;

   return 0 ;
}

Compile & run:

A circle with a radius of 20cm has an area of: 1256 cm.

 

 

#define can also be used as a macro which could take arguments:

#include <iostream>
using namespace std;

#define MIN(a,b) ( ( (a) < (b) ) ? a : b )  //using the ? ternary operator

int main ()
{
   int val1 = 22, val2 = 64 ;

   cout <<"The minimum is " << MIN( val1, val2 ) << endl;

    return 0;
}

Compile & Run:

The minimum is 22

Predefined Macros

Macro Description
__DATE__ The current date as a character literal in "MMM DD YYYY" format
__TIME__ The current time as a character literal in "HH:MM:SS" format
__FILE__ This contains the current filename as a string literal.
__LINE__ This contains the current line number as a decimal constant.
__STDC__ Defined as 1 when the compiler complies with the ANSI standard.
#include <iostream>
using namespace std;

int main () {

	cout << "File " << __FILE__ << endl ;
	cout << "Date " << __DATE__ << endl ;
	cout << "Time " << __TIME__ << endl ;
	cout << "Line " << __LINE__ << endl ;
	cout << "ANSI " << __STDC__ << endl ;

	return 0;
}

Compile & run:

File ..\working.cpp
Date Apr 5 2013
Time 10:32:46
Line 9
ANSI 1

Input / Output Streams

I/O is provided via the standard library.

 

The ios class is derived from ios_base

 

The istream class (derived from ios) is used for input streams. The extraction operator >> is used to remove values from the stream

 

The ostream class (derived from ios) is used for output streams. The insertion operator << is used to place values into the stream

 

The iostream is used for both input and output streams, and is most commonly used.

 

I/O methods are contained within the std namespace, and hence 'using namespace std ;' is utilised to avoid having to write std:: before each method, e.g. std::cin

 

Standard streams:

  • cin - istream_withassign class associated with the standard input: the keyboard
  • cout - ostream_withassign class associated with the standard output: the monitor
  • cerr - ostream_withassign class associated with the standard output: the monitor, unbuffered
  • clog - ostream_withassign class associated with the standard output: the monitor, unbuffered

Basic example showing standard streams used for input and output:

#include <iostream>
using namespace std;

int main () {

	cout << "Please enter the product of 6 times 7:" << endl ;

	int number ;

	cin >> number;

	if (number != 42){
		cerr << "Hmm, perhaps you should go back to school!" << endl ;
		return 1 ;
	}

	cout << "Excellent! " << number << " is correct." << endl ;

	return 0;
}

Compile & Run:

Please enter the product of 6 times 7:
13
Hmm, perhaps you should go back to school!

istream Input

Requires #include <istream> ; or #include <iostream> preprocessor directive.

 

The extraction operator >> is used to read information from the input stream.

 

Can be overloaded for specific purposes (e.g. for an object of a specific class data type).

 

A few problems may arise that can overcome by the use of manipulators:

  • endl - (in iostream) prints new line and flushes any buffered output
  • setw() - (in iomanip) limits number of characters being read
  • getch() - gets a character from the input stream (including whitespace)
  • getline() - as above but also gets the newline
  • gcount() - returns the numbers of characters read
  • ignore() discards the first character in the stream
  • ignore(number) discards the first specified 'number' parameter of characters
  • peek() allows you to read a character from the stream without removing it from the stream
  • unget() returns the last character read back into the stream so it can be read again by the next call
  • putback(character) allows you to put the specified character parameter of your choice back into the stream to be read by the next call
#include <iostream>
#include <iomanip>
using namespace std;

int main () {

	cout << "Please enter the first three characters of your name:" << endl ;

	char name[3];

	cin >> std::setw(3) >> name ;

	cout << "Notice that a null terminator has taken the last position in the char array, " << endl ;
	cout << "thus only returning the first two characters: "<< name << endl ;

	return 0;
}

Compile & Run:

Please enter the first three characters of your name:
Der
Notice that a null terminator has taken the last position in the char array,
thus only returning the first two characters: De

 

 

Using getline() to get the whole line including whitespace:

#include <iostream>
#include <string>
using namespace std;

int main () {

	string myString;

	cout << "Please enter an you full name: " << endl ;

	getline(cin, myString) ;

	cout << "Hi " << myString << "!" << endl ;
	cout << "What's the first line of your address?" << endl ;

	getline(cin, myString) ;

	cout << "Thanks for confirming " << myString << " as your address." << endl ;

	return 0;
}
Please enter an you full name:
Andrea Dovizioso
Hi Andrea Dovizioso!
What's the first line of your address?
21 Coconut Grove
Thanks for confirming 21 Coconut Grove as your address.

ostream Output

Requires #include <ostream> ; or #include <iostream> preprocessor directive.

 

The insertion operator << is used to send information to the output stream.

 

Can be overloaded for specific purposes (e.g. for an object of a specific class data type).

 

A few problems may arise that can overcome by the use of flags and manipulators:

  • setf(parameter) - used to set the flag as indicated by the parameter
  • unsetf(parameter) - turns off the flag as indicated by the parameter

 

Flags live in the ios class, manipulators lives in the std namespace, and the member functions live in the ostream class.

 

  • Flags - used within setf() of unsetf()
    • (ios::boolaplpha) - Prints "true" of "false" boolean values
    • (ios::noboolapha) - Prints 1 or 0 boolean values (default
    • (ios::showpos) - Prefix positive numbers with a +
    • (ios::noshowpos) - Don't show positive numbers with a +
    • (ios::uppercase) - Use upper case letters
    • (ios:: dec, ios::basefield) - Prints values in decimal (default)
    • (ios:: hex, ios::basefield) - Prints values in hexadecimal
    • (ios:: oct, ios::basefield) - Prints values in octal
  • Manipulators
    • boolaplpha - Prints "true" of "false" boolean value
    • noboolapha - Prints 1 or 0 boolean values (default)
    • showpos - Prefix positive numbers with a +
    • noshowpos - Don't show positive numbers with a +
    • uppercase - Use upper case letters (for dec, hex, oct, scientific notation - not strings!)
    • nouppercase - Use lower case letters
    • dec - Prints values in decimal (default)
    • hex - Prints values in hexadecimal
    • oct - Prints values in octal
    • fixed - Use decimal notation for values
    • scientific - Use scientific notation for values
    • showpoint - Show a decimal point and trailing zeroes for floating point values
    • noshowpoint - Don't show a decimal point and trailing zeroes for floating point values
    • setprecision(number) - Sets the precision to 'number' for floating point numbers
    • internal - Left justifies the sign and right justifies the number
    • left - Left justifies the sign and number
    • right - Right justifies the sign and number
    • setfill(character) - Set the specified character parameter as the fill character
    • setw(number) - Sets the field width for input and output of the specified number parameter
  • Methods
    • precision() - Returns the current precision of floating point numbers
    • precision(number) - Sets the precision of floating point numbers to 'number' and returns the previous precision
#include <iostream>
#include <iomanip>
using namespace std;

int main () {

	int number;

	cout << "Please enter an integer:" << endl ;
	cin >> number ;

	cout.setf(ios::showpos); //turn on the ios::showpos flag
	cout << "Notice the + symbol now being printed: " << number << endl ;

	cout.unsetf(ios::dec); //turn off decimal output
	cout.setf(ios::oct) ; //turn on octal output
	cout << "Now printing in octal: " << number << endl ;

	cout.unsetf(ios::oct); //turn off octal output
	cout.setf(ios::hex) ; //turn on hexadecimal output
	cout << "Now printing in hex: " << number << endl << endl ;
	cout.unsetf(ios::hex); //turn off hex output

	cout << "Now using manipluators!" << endl ;
	cout << dec << number << endl ;
	cout << oct << number << endl ;
	cout << hex << number << endl ;

	cout << "Booleans: " << endl ;
	cout << boolalpha << true << " " << false << endl ;
	cout << noboolalpha << true << " " << false << endl ;

	return 0;
}

Compile & Run:

Please enter an integer:
42
Notice the + symbol now being printed: +42
Now printing in octal: 52
Now printing in hex: 2a 

Now using manipluators!
+42
52
2a
Booleans:
true false
1 0

stringstream

Requires #include <sstream> ; preprocessor directive.

 

Enables string based objects to be treated as a stream, thus allowing extraction/insertion to/from strings.

 

Useful for converting strings to numbers and vice-versa.

 

 

String objects (being acquired via getline() function) are converted to numerical values:

#include <iostream>
#include <string>
#include<sstream>
using namespace std;

int main () {

	string myString ;

	int laps = 0 ;

	float time = 0 ;

	cout << "Please enter the number of laps completed:" << endl ;

	getline(cin, myString) ;

	stringstream(myString) >> laps ;

	cout << "Please enter the time taken to complete those laps:" << endl ;

	getline(cin, myString) ;

	stringstream(myString) >> time ;

	cout << "Average lap speed was: " << time / laps << endl ;

	return 0;
}

Compile & Run:

Please enter the number of laps completed:
28
Please enter the time taken to complete those laps:
345
Average lap speed was: 12.3214

Input Validation

Process of checking user input against specified criteria.

 

Two types:

  • String
    • Accept all user input as a string, then accept/reject that string based on its format
  • Numerical
    • Accept/reject user entered value  is within a specified range

 

The cctype header file provides the following member functions:

 

Function Meaning
isalnum(int) Returns non-zero if the parameter is a letter or a digit
isalpha(int) Returns non-zero if the parameter is a letter
iscntrl(int) Returns non-zero if the parameter is a control character
isdigit(int) Returns non-zero if the parameter is a digit
isgraph(int) Returns non-zero if the parameter is printable character that is not whitespace
isprint(int) Returns non-zero if the parameter is printable character (including whitespace)
ispunct(int) Returns non-zero if the parameter is neither alphanumeric nor whitespace
isspace(int) Returns non-zero if the parameter is whitespace
isxdigit(int) Returns non-zero if the parameter is a hexadecimal digit (0-9, a-f, A-F)

 

 

String Validation

 

This example asks the user to enter their name and checks the string for alpha or space characters, or set the reject boolean if not:

#include <iostream>
#include <string>
#include <cctype>
using namespace std;

int main () {

	cout << "Please enter name:" << endl ;
	string userName ;
	getline(cin, userName) ;

	bool rejected = false ; //set a boolean to check against

	for (unsigned int i = 0; i < userName.length() && !rejected ; i++) {

		if (isalpha(userName[i])) //check for alpha characters
			continue;

		if (userName[i] == ' ') //check for spaces
			continue;

		rejected = true ; //set rejected if anything apart from alpha or space characters
	}

	if (!rejected){
		cout << "Thanks for entering only alpha and/or spaces" << endl ;
	} else {
		cout << "Next time please don't enter numbers or punctuation." << endl ;
	}

	return 0;
}

Compile & Run:

Please enter name:
Derrick
Thanks for entering only alpha and/or spaces

 

 

Numeric Validation

 

This example asks the user to enter their birth year and checks the number entered via the stream extractor to see if it falls with the specified range:

#include <iostream>
#include <string>
#include <cctype>
using namespace std;

int main () {
	int birthYear;

	while (true) {
	cout << "Please enter the year you were born in:" << endl ;

	cin >> birthYear;

	if (cin.fail()) { //no data entered

		cin.clear() ; //reset state bits
		cin.ignore(1000, '\n') ; //clear out bad stream input
		continue ;
	}

	if (birthYear < 1900  && birthYear > 2013) //make sure birthYear is realistic
		continue ;

	break ;
	}

	cout << "Thanks! You must be about " << 2013 - birthYear << " years old now!" << endl ;

	return 0;
}

Compile & Run:

Please enter the year you were born in:
1970
Thanks! You must be about 43 years old now!

File I/O

Requires #include <fstream> ; preprocessor directive.

 

The ofstream can be used for output to create files and write data to them.

 

The ifstream can be used for input to read data from files.

 

A file must be opened before it can be read from or written to.

 

File output using the ofstream class:

#include <iostream>
#include <fstream>
using namespace std;

int main () {

	ofstream myFile("testFile.dat") ;

	if (!myFile){
		cerr << "Couldn't open file for writing." ;
		return 1 ;
	}

	myFile << "Here's the first line of text!" << endl ;

	myFile << "Here's another line of text!" << endl ;

	return 0;
}

Compile & Run will create the specified file 'testFile.dat' that contains the following two output lines:

Here's the first line of text!
Here's another line of text!

 

 

File input using the ifstream class to open and read the above created file:

#include <iostream>
#include <fstream>
#include <string>
using namespace std;

int main () {

	ifstream myFile("testFile.dat") ;

	if (!myFile){
		cerr << "Couldn't open file for reading." ;
		return 1 ;
	}

	while (myFile) {

		string fileData ;
		getline(myFile, fileData) ;
		cout << fileData << endl ;
	}

	return 0;
}

Compile & Run:

Here's the first line of text!
Here's another line of text!

 

 

File Modes

Ios file mode Meaning
app Opens the file in append mode
ate Seeks to the end of the file before reading/writing
binary Opens the file in binary mode (instead of text mode)
in Opens the file in read mode (default for ifstream)
nocreate Opens the file only if it already exists
noreplace Opens the file only if it does not already exist
out Opens the file in write mode (default for ofstream)
trunc Erases the file if it already exists

 

 

Append text to a file using the ios:: app to append the text to the end of the file:

#include <iostream>
#include <fstream>
using namespace std;

int main () {

	ofstream myFile("testFile.dat", ios:: app) ;

	if (!myFile){
		cerr << "Couldn't open file for writing." ;
		return 1 ;
	}

	myFile << "Wow, yet another line of amazing text" << endl ;

	myFile << "Even more text..." << endl ;

	return 0;
}

Compile & Run will append the specified text to 'testFile.dat' that now contains the following four output lines:

Here's the first line of text!
Here's another line of text!
Wow, yet another line of amazing text
Even more text...

 

 

Closing and reopening files:

#include <iostream>
#include <fstream>
using namespace std;

int main () {

	ofstream myFile("testFile.dat", ios:: app) ;

	if (!myFile){
		cerr << "Couldn't open file for writing." ;
		return 1 ;
	}

	myFile << "This is the 5th line of text" << endl ;

	myFile << "How much more text can we take!" << endl ;

	myFile.close() ;

	myFile.open("testFile.dat", ios::app) ;

	myFile << "OK this really is the last line of text" << endl ;

	return 0;
}

Compile & Run will append the specified text to 'testFile.dat' that now contains the following output lines:

Here's the first line of text!
Here's another line of text!
Wow, yet another line of amazing text
Even more text...
This is the 5th line of text
How much more text can we take!
OK this really is the last line of text

 

 

Stream state member functions in ios

  • good() - checks whether the stream is ready for input/output operations
  • bad() - returns true if reading / writing operations fail
  • fail() - returns true, in a similar manner as bad(), but more specifically upon formatting errors (e.g. when an alphabetical character is extracted instead of a number)
  • eof() - returns true if opened file is at the end
  • good() - returns false, if any of the above would return true
  • clear()  - takes no parameters, and is used reset the state flags
  • clear(state)  - clears all flags and sets the state flag passed in
  • rdstate() - returns the currently set flags
  • setstate(state) - sets the state flag passed in

File pointers

aka Stream pointer.

 

Each file stream class has a pointer that points to the current read/write position in the file.

 

Default position when opening a file for reading/writing is at the beginning of the file, unless in append mode in which case the file pointer is moved to the end of the file in order not to overwrite any current data.

 

The file input seekg() and file output seekp() methods require two parameters that firstly specify how many bytes to move the file pointer and the secondly an ios flag that specifies where to offset from:

  • beg - Offset is relative to the beginning of the file (default)
  • cur - Offset is relative to current position
  • end - Offset is relative to the end of the file

A positive offset means move the file pointer forward towards the end of the file, whilst a negative offset means move the file pointer backward towards the beginning of the file.

 

Using the previously made testFile.dat, this example reads out the lines from the specified file pointer positions:

#include <iostream>
#include <fstream>
#include <string>
using namespace std;

void printLine(ifstream &myFile, string myString) {
	getline(myFile, myString) ;
	cout << myString << endl ;
}

int main () {

	ifstream myFile("testFile.dat") ;

	if (!myFile){
		cerr << "Couldn't open file for reading." ;
		return 1 ;
	}

	string myString ;

	myFile.seekg(7) ; //more file pointer to 7th character
	printLine(myFile, myString);

	myFile.seekg(6, ios::cur) ; //more file pointer 6 more characters forward
	printLine(myFile, myString);

	myFile.seekg(0, ios::beg) ; //more file pointer to beginning of file
	printLine(myFile, myString);

	myFile.seekg(-41, ios::end) ; //more file pointer 6 more characters forward
	printLine(myFile, myString);

	return 0;
}

Compile & Run:

t another line of amazing text
xt...
Wow, yet another line of amazing text
OK this really is the last line of text

 

 

Two further functions, tellg() and tellp() take no parameters and return the position of the file pointer:

#include <iostream>
#include <fstream>
using namespace std;

int main () {

	ifstream myFile("testFile.dat") ;

	if (!myFile){
		cerr << "Couldn't open file for reading." ;
		return 1 ;
	}

	myFile.seekg(0, ios::end) ;

	cout << "The file size is: "<< myFile.tellg() << "Bytes" << endl ;

	return 0;
}

Compile & Run:

The file size is: 220Bytes

Exceptions

An Exception is an error or unpredictable behaviour (like run-time errors) within a program that is not caused by the OS.

 

Exception handling is the ability to deal with these problems, and allows the program to manage the problem rather than simply crashing.

 

The general technique is to use a try block and when an exception arises, it is thrown to the catch exception handler. If no exceptions are thrown, the code continues normally and all handlers are ignored.

 

Three keywords:

  • try { desired behaviour code block }
    • The normal behaviour is contained within the code block
  • catch ( parameters ) { code-to-execute}
    • The actual exception handlers
    • Always follows the try section, and must not have any code between the two
    • parameter(s) are passed in by the previous try section
    • ellipses can be used as a 'catch all' for non-defined data-types in the catch parameter. e.g. catch(...)
    • Deals with the problem. e.g. display error message
  • throw data-type-value
    • used to signal an error has occurred of the data-type-value specified
    • Typically this value is an error code, or description, or custom code

 

This example sends a value to a function, which then tests the value in the try block, then throws an exception according to the value test result, which is then caught by one of the catch blocks:

#include <iostream>
using namespace std;

void myExceptFunc(int test) {

	try {
		if (test==0)throw test;
		if (test==1)throw 'a';
		if (test==2)throw 1233.33;
	}

	catch(int i) {
		cout << "Exception caught of data type int, with a value of " << i << endl ;
	}

	catch(double n) {
		cout << "Exception caught of data type float, with a value of: " << n << endl ;
	}

	catch(...) {
		cout << "Exception caught using ellipses for default, non-specified data types!" << endl ;
	}
}

int main() {

	myExceptFunc(0); //send 0 by value to myExceptFunc()
	myExceptFunc(1); //send 1 by value to myExceptFunc()
	myExceptFunc(2); //send 2 by value to myExceptFunc()

	return 0;

}

Compile & Run:

Exception caught of data type int, with a value of 0
Exception caught using ellipses for default, non-specified data types!
Exception caught of data type float, with a value of: 1233.33

 

 

 

Another example:

#include <iostream>
using namespace std;

double divisor(double a, double b) {

	if (b == 0)
		throw "Cannot divide by zero!" ;

	return a / b ;
}

int main() {

	float x, y ;

	cout << "Please enter the first number to be divided: " << endl ;

	cin >> x ;

	cout << "Please enter the divisor: " << endl ;

	cin >> y ;

	try {
		cout << x << " divided by " << y << " equals: " << divisor(x, y) << endl ;
	}

	catch (const char *stringException) {
		cerr << "Error: " << stringException << endl ;
	}

	return 0;

}

Compile & Run:

Please enter the first number to be divided:
123.456
Please enter the divisor:
0
Error: Cannot divide by zero!

 

 

Standard exceptions defined in the #include <exception> header file. Note all are within the std namespace:

Exception Description
std::exception An exception and parent class of all the standard C++ exceptions.
std::bad_alloc This can be thrown by new.
std::bad_cast This can be thrown by dynamic_cast.
std::bad_exception This is useful device to handle unexpected exceptions in a C++ program
std::bad_typeid This can be thrown by typeid.
std::logic_error An exception that theoretically can be detected by reading the code.
std::domain_error This is an exception thrown when a mathematically invalid domain is used
std::invalid_argument This is thrown due to invalid arguments.
std::length_error This is thrown when a too big std::string is created
std::out_of_range This can be thrown by the at method from for example a std::vector and std::bitset<>::operator[]().
std::runtime_error An exception that theoretically can not be detected by reading the code.
std::overflow_error This is thrown if a mathematical overflow occurs.
std::range_error This is occured when you try to store a value which is out of range.
std::underflow_error This is thrown if a mathematical underflow occurs.

Templates

2 types:

  • Function templates
  • Class templates

Function templates act like a stencil without having to specify the exact data-types of some / all of the variables. Instead the function is defined using placeholder data-types known as template type parameters.

 

When a function template is called, the compiler stencils in a copy of the template replacing the placeholder data-types with the actual variable data-types in the function call. Thus multiple data-types can be sent as parameters to the function template.

 

When the compiler encounters a template function call, it replicates the template function and replaces the template type parameters with the actual data types; this is known as a function template instance.

 

Here is the general syntax for a function template:

template <typename Type>  //template data-type parameter declaration
ret-type funcName(Type param1, Type param2 ) {
	...func code...
}

*note: need to check, but it seems the function template must follow immediately after the template parameter declaration

 

 

The keyword template informs the compiler that what follows is a list of template parameters, which are placed within <angled brackets>.

 

To create a template parameter the keyword typename or class before your chosen data-type placeholder name.

*(note the keyword class is quite used interchangeably, and makes little difference in most contexts).

**(also note that traditionally an uppercase "T" is used to denote the name "Type" or data-type - however, for simplicity I have chosen to use myType in the following example)

 

This example declares a template parameter to be used as the placeholder, which is then replaced when the function is called, based on the data-type of the parameters being sent to the template function:

#include <iostream>
using namespace std;

template <typename myType> // this is the template parameter declaration
myType funcMax(myType x, myType y) {  //now using myType

	return (x > y) ? x : y;
}

int main() {

	int a = 12, b = 17 ;
	cout << funcMax(a, b) << endl ;

	double c = 36.42, d = 17.93 ;
	cout << funcMax (c, d) << endl ;

	char e = 'j', f = '11' ;
	cout << funcMax(e, f) << endl ;

	return 0;

}

Compile & Run:

17
36.42
j

 

*note: when creating the above, I found out that the standard library already has a templated max() function, and since I have the statement "using namespace std ;" the compiler was unable to tell which version of the max() function I required. I therefore changed mine in the example above to funcMax() ;

 

 

Class templates pretty much follow the same logic as function templates by replacing the templated self defined data-type into the class

 

#include <iostream>
using namespace std ;

template <class myType>
class Maximus {
private:
	myType x, y ;  //here we see the generic myType being used
public:
	Maximus (myType alpha, myType beta){ //again the generic myType
		x = alpha ;
		y = beta ;
	}
	myType getMax() ; //and again within the prototype
};

template <class myType>
myType Maximus<myType>::getMax(){ //the method definition also using the generic myType
	return (x > y) ? x : y;
}

int main() {
	//now an object of the Maximus class data-type is instantiated
	Maximus <int> myObject(42, 17) ;  //using Ints in the definition
	cout << myObject.getMax() << endl ;;

	Maximus <double> newObject(123.456, 789.012) ; //this time using doubles in the definition
	cout << newObject.getMax() << endl ;

	Maximus <char> nextObject('j', '11') ; //this time using chars in the definition
	cout << nextObject.getMax() << endl ;

	return 0;
}

Compile & Run:

42
789.012
j