0% found this document useful (0 votes)
23 views

Computer Methods Lecture 2

The document discusses extending a C++ class by adding static member variables, static member functions, and a destructor. It demonstrates how to declare and define these features in the class header and implementation files. Static members allow sharing data between all class instances, while the destructor indicates when an object is destroyed.
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
23 views

Computer Methods Lecture 2

The document discusses extending a C++ class by adding static member variables, static member functions, and a destructor. It demonstrates how to declare and define these features in the class header and implementation files. Static members allow sharing data between all class instances, while the destructor indicates when an object is destroyed.
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 39

Computer Methods 3

ENEL3CCH1
Week Three
Extending the sample application
A deeper look at functions

Discipline of Electrical, Electronic & Computer Engineering Bashan Naidoo


What we cover in week 3
• We extend the C++ class definition (Basic)
• Static member vars
• Static methods
• Destructor

• Instantiating an object of a class


• Using static members for passing messages
• Using a destructor

• Addressing some issues noted in class and labs


• Function overloading
• Function templates
• Class templates (week 4)
MyProg.cpp
#include <iostream>
#include <string>
#include “Message.h”
int main(){
Message MsgObj("Howzit!"); //instantiate the object
MsgObj.display(); //use its method
return 0;
}

Message.h Message.cpp
Class header file #include “Message.h”
#pragma once
#include <iostream> Declares the class
#include <string> void Message::setMsg(std::string Msg1) { this->Msg = Msg1; }
interface… everything
the caller needs to
class Message { std::string Message::getMsg() { return Msg; }
know about the class
private:
std::string Msg; void Message::display() {
std::cout << Msg;
public: }
void setMsg(std::string Msg1);
std::string getMsg(); Message::Message() { //define default constructor
void display(); this->Msg = "Empty string!!!";
Message(); //default constructor }
Message(std::string); //construct overload Class implementation file
}; Message::Message(std::string Msg) {
this->Msg = Msg; Contains method
} implementations for the class
Let us extend the OOP demo
• Add static member vars
These are common to all instances of the class; so if one object modifies a static var, all other instances of that
class will see the change. It is a shared resource.

For example:
Use this to count the total number of messages sent by all instances of the class (do this as an execrcise)
Or use this to count the number of class instances currently in existence

• Add static member functions


These can only access static member vars.

For example:
Use this to print the total number of messages sent by all instances of the class (do this as an exercise)
Or to print the total number of class instances in existence

• Add a destructor
that indicates the destruction of an object and the number of class instances
still left.
Static var initialisation…
• Not optional!
Create and use a static member variable • Not inside any function
Message.cpp
#include “Message.h”
Message.h int Message::NumOfMessageObjects = 0;
Static is a storage class…
#pragma once • Only inserted into the declaration
#include <iostream> • Not inserted into the definition void Message::setMsg(std::string Msg1) { this->Msg = Msg1; }
#include <string>
std::string Message::getMsg() { return Msg; }
class Message {
private: void Message::display() {
std::string Msg; std::cout << Msg;
static int NumOfMessageObjects; }
…with
public: class
void setMsg(std::string Msg1); scope Message::Message() { //define default constructor
std::string getMsg(); Msg = "Empty string!!!“;
NumOfMessageObjects++;
void display(); }

Message(); //default constructor Message::Message(std::string Msg) { Use


Message(std::string); //construct overload this->Msg = Msg;
NumOfMessageObjects++;
}; }

Declare Use
Message.cpp
Add a static member function…
#include “Message.h”

int Message::NumOfMessageObjects = 0;
Message.h
void Message::setMsg(std::string Msg) { this->Msg = Msg; }
#pragma once
#include <iostream> std::string Message::getMsg() { return Msg; }
#include <string>
void Message::display() { Static method…
class Message { std::cout << Msg; • Can only use static vars
private: } • cannot use this->
std::string Msg;
static int NumOfMessageObjects; int Message::ObjectCount() {
return NumOfMessageObjects;
public: }
void setMsg(std::string Msg1);
std::string getMsg(); Message::Message() { //define default constructor
Msg = "Empty string!!! ";
void display(); NumOfMessageObjects++;
static int ObjectCount(); }

Message(); //default constructor Message::Message(std::string Msg) {


Message(std::string); //construct overload this->Msg = Msg;
NumOfMessageObjects++;
}; }
Static is a storage class…
• Only inserted into the declaration
• Not inserted into the definition
Message.cpp
#include “Message.h”
Add a destructor
int Message::NumOfMessageObjects = 0;
void Message::setMsg(std::string Msg1) { this->Msg = Msg1; }
std::string Message::getMsg() { return Msg; }
Message.h
#pragma
#pragma once
once void Message::display() {
#include
#include <iostream>
<iostream> std::cout << Msg;
#include
#include <string>
<string> }

class
class Message
Message {{ int Message::ObjectCount() {
private:
private: return NumOfMessageObjects;
std::string
std::string Msg;
Msg; }
static
static int
int NumOfMessageObjects;
NumOfMessageObjects;
Message::Message() { //define default constructor
public:
public: Msg = "Empty string!!! ";
void
void setMsg(std::string
setMsg(std::string Msg1);
Msg1); NumOfMessageObjects++;
std::string
std::string getMsg();
getMsg(); }

void
void display();
display(); Message::Message(std::string Msg) {
static
static int
int ObjectCount();
ObjectCount(); this->Msg = Msg;
NumOfMessageObjects++;
Message();
Message(); //default
//default constructor
constructor }
Message(std::string);
Message(std::string); //construct
//construct overload
overload
~Message();
~Message(); //destructor
//destructor Message::~Message() {
};
}; std::cout << --NumOfMessageObjects << " remaining objects/n";
}
MyProg.cpp
#include <iostream> Remember the lecture 2 exercise…
#include <string>
#include "Message.h" Use static vars and methods to count
int main() {
and display the total number of
Message MsgObj1("Howzit! \n"); messages send by all instances of the
std::cout << "Object " << MsgObj1.ObjectCount() << ": "; Message class.
MsgObj1.display();

Message MsgObj2("Hundreds Bro! \n"); Compete this exercise now


std::cout << "Object " << MsgObj2.ObjectCount() << ": ";
MsgObj2.display();

std::cout << "\n That’s all folks!\n";


return 0;
}
Static member variables…
• Static member vars belong to the CLASS and not to the object
so…
• There is only one instance of the static var in the whole application…
• All instances of the class share access to this one static var…

• If any instance of the class modifies the static var, all other
instances of the class will see the modification…
Static member variables…
• Static members vars are initialised BEFORE any object is created

• Static member vars are… Obviously, non-static vars…

• declared inside the class, • have object scope


(limited to one object instance)
• initialised OUTSIDE the class (and scoped back in).
• And automatic lifetime
(ie. created and destroyed with
the object instance to which it
• Static member vars have… belongs)

• Class scope: They are seen my all instances of the class


• Static lifetime: Its lifetime is the entire duration of the program
Static member functions…
• Static member functions belong to the CLASS and not to the
object so…
• There is only one instance of the static function…
• All instances of the class share access this one static function…

• Because the static function does not belong to any class


instance, it…
• CANNOT access member vars that belong to OBJECTS of the class…
• Can ONLY access static member vars belonging directly to the CLASS
Now we have seen by example that…

A
OOP is not a language methodology

OOP is a way of thinking

OBJECTs
are central to OOP
What is OOP?
One must understand the philosophical basis of OOP before the
its syntactical expression in C++.

Philosophical
Abstraction concepts Inheritance

Polymorphism

Encapsulation Object

Class
Common issues noted in class and labs

Poor understanding of scopes and scoping principles

• Function definition in the wrong place

• Prototypes missing or in the wrong place


Common issues noted in class and labs
Poor understanding of OOP philosophy

• Parameterised constructor defined with no default constructor


What happens if the object is instantiated without parameters?

• Classes not properly abstracted from the initial use-case


Classes are written for the specific use-case when they should be
written for the use-class (made more reusable across multiple use-cases)

For example: I created the Message class and used it in the Howzit
example. I made the class generic and reusable. It can deliver any
message. It is not a Howzit class! It is more generic.
Common issues noted in class and labs
Poor understanding of OOP philosophy

• Encapsulation is often improperly deployed


• Member vars are sometimes public when they should be private.
• Methods sometimes take parameters that should rather be private
members. That is: data is not passed to the class via accessors rather
it is passed directly to methods.

Why do we have private data with public accessor methods?


What is the thinking behind this?
Common issues noted in class and labs
Keep these issues in mind as we move forward.

We address the issues while extending our understanding into


the following basic concepts…

• Function overloading

• Function templates
Special cases of
overloading
• Class templates
Function overloading

In this section we review…


• Parameters vs Arguments
• Function Prototypes
• Function Signatures
• Function Definition

…in order to understand function overloading


Towards function overloading…
What are parameters and arguments?

Functions are defined with Parameters (variables or identifiers)

std::string MyMessage(int msg) {


if (msg == 1) return "Howzit!";
return "Hello World!";
}

Functions are called with arguments (values or data)


int main()
{
std::cout << MyMessage(1) << "\n";
}
Towards function overloading…
Function prototypes
• Purpose
• Declares the existence of a function
• It is optional depending on… where the function is defined
• Initiates function scope
• Used to validate call syntax Where do we typically insert
prototypes in C? Why?

• Where to insert a prototype?


• At the point where function scope should begin (assuming the
function is defined elsewhere)
Towards function overloading…
Function prototypes
#include <iostream>
• Will this compile? #include <string>

int main()
{
NO… why not? std::cout << MyMessage(1) << "\n";
}
Function
• Function scope only begins std::string MyMessage(int msg) {
scope starts
after the function call in main. if (msg == 1) return "Howzit!";
here
return "Hello World!";
}
• What are the two ways to fix
this?
Towards function overloading…
Function prototypes
#include <iostream>
#include <string>
• Will this compile?
std::string MyMessage(int msg) { Function
if (msg == 1) return "Howzit!"; in scope
YES return "Hello World!";
}

int main()
• Function is in scope at the call {
std::cout << MyMessage(1) << "\n";
}
Towards function overloading…
Function prototypes All prototypes
• This too will work… #include <iostream>
• Prototype declares function #include <string>
near the top of the file std::string MyMessage(int); Function in
scope
int main()
• Prototype therefore gives {
function “file” scope std::cout << MyMessage(1) << "\n";
}

• So function is in scope inside std::string MyMessage(int msg) {


if (msg == 1) return "Howzit!";
of main() even though it is return "Hello World!";
defined after main() }
Towards function overloading…
Function signatures

If function overloading did not exist;


then function names can be used as a function identifier. This is sufficient.

If we use function overloading;


the name is no longer an adequate identifier for the function. We must
consider the parameter list + name. This is called a function signature.
Towards function overloading…
Function signatures
• Purpose
• Provides us with an extended function identifier that permits overloading

• What is it?
• A prototype without a return type (function name + parameter type list)

• Implications
• Function name need not be unique anymore
• Function signature must be unique
• Signatures can only differ in the number and type of parameters
Towards function overloading…
Function signatures
• Example
Function definition
double addNum(int a, double b) { return a + b; }

Function prototype
double addNum(int, double);

Function signature
addNum(int, double)

Function call
std::cout << "Sum of 6 + 4.56 = " << addNum(6,4.56) << std::endl;
Towards function overloading…
Function signatures
Signatures are unique
• Example Therefore the overloading is acceptable
Happy compiler!

Definitions (overloaded addNum) Signatures


double addNum(int a, double b) { return a + b; } addNum(int, double)
double addNum(double a, int b) { return a + b; } addNum(double, int)
Start of
compilation
Each function overload produces
a signature on compilation
Function call

std::cout << "Sum of 6 + 4.56 = " << addNum(6,4.56) << std::endl;


Towards function overloading…
Function signatures
• Example
…and executes the matching function

Definitions (overloaded addNum) Signatures


double addNum(int a, double b) { return a + b; } addNum(int, double)
double addNum(double a, int b) { return a + b; } addNum(double, int)

Compiler finds the


matching signature…
Function call

std::cout << "Sum of 6 + 4.56 = " << addNum(6,4.56) << std::endl;


Towards function overloading…
Function signatures

4 ways to differentiate function signatures


1. Different function names (not applicable for overloading)
2. Parameters with different types
3. Different number of parameters
4. Different order of parameter types
Towards function overloading…
Function Definition

• Obviously… the function definition is not optional

• Function definition can be anywhere in the source file as long as the


prototype is in the correct place (in scope)

• If there is no prototype, then define the function where the prototype


should be – consider scope

• Function definitions and prototypes can be in separate files and these


must be included (#include<>) in the correct place
Towards function overloading…
#include <iostream>
Defined up here
int addNum(int a, int b) { return a + b; }
char addNum(char a, char b) { return a + b; }
double addNum(double a, double b) { return a + b; } In scope
int main(){
down here
int a = 650, b = 430;
std::cout << "Int sum of 650 + 430 = " << addNum(a,b) << std::endl;

char c = 20, d = 63;


std::cout << "Char sum of 20 + 64 = " << addNum(c, d) << std::endl;

double e = 260.555, f = 440.333;


std::cout << "Double sum of 260.555 + 440.333 = " << addNum(e, f) << std::endl;
}
Towards function overloading…
#include <iostream>
• Overloaded function addNum()
int addNum(int a, int b) { return a + b; }
• Only datatypes differ
char addNum(char a, char b) { return a + b; } • Logic & structure are identical
double addNum(double a, double b) { return a + b; }

int main(){
. This is a special case of overloading
.
.
.
.
What if we had an adjustable
. datatype (like “variant” in VB)
}

Then we can replace the above


code with one function definition
Towards function overloading…
Function templates
int addNum(int a, int b) { return a + b; }
char addNum(char a, char b) { return a + b; }
double addNum(double a, double b) { return a + b; }

All of this can be replaced by…

template<typename myVar>
myVar addNum(myVar a, myVar b) { return a + b; }

or by…
template<class myVar>
myVar addNum(myVar a, myVar b) { return a + b; }
Towards function overloading…
Function templates
#include <iostream>

template<typename myVar>
myVar addNum(myVar a, myVar b) { return a + b; }

int main()
{
int a = 650, b = 430;
std::cout << "Int sum of 650 + 430 = " << addNum(a,b) << std::endl;

char c = 20, d = 63;


std::cout << "Char sum of 20 + 64 = " << addNum(c, d) << std::endl;

double e = 260.555, f = 440.333;


std::cout << "Double sum of 260.555 + 440.333 = " << addNum(e, f) << std::endl;
}
Towards function overloading…
Function templates
What if we have multiple types?

double addNum(int a, double b) { return a + b; }

template<typename myType1, typename myType2>


myType2 addNum(myType1 a, myType2 b) { return a + b; }

This will work, but be careful!!! There are potential issues…


Towards function overloading…
Function templates
Consider the template…
template<typename myArg1, typename myArg2>
myArg2 addNum(myArg1 a, myArg2 b) { return a + b; }

The template works for any numeric type


So both of the following (and more) will apply

double addNum(int a, double b) { return a + b; }


int addNum(double a, int b) { return a + b; }
As long as the types are numeric and
The second parameter and return value have the same type
Towards function overloading…
Function templates
Consider the code…
What is the output and how/why is it problematic?
#include <iostream>

template<typename myArg1, typename myArg2>


myArg2 addNum(myArg1 a, myArg2 b) { return a + b; }

int main(){
std::cout << "Sum of 65 + 43.56 = " << addNum(65, 43.56) << std::endl;
std::cout << "Sum of 65 + 43.56 = " << addNum(43.56, 65) << std::endl;
}
Towards function overloading…
Function templates - notes

• main() is a function but you cannot use templates for main()


• The template parameter list <> cannot be empty
• Function parameter list () must use all template parameters <>

eg. template<typename myArg1, typename myArg2>

used used

myArg2 addNum(myArg1 a, myArg2 b) { return a + b; }


Towards function overloading…
Function templates - summary

When do we use templates instead of overloading?


• If the number and order of parameters is fixed and
• If the program logic is identical for any datatype
• Only the datatypes vary

Templates are similar to overloading but they are…


• Less flexible
• More efficient in terms of code size

You might also like