Classes and Objects

Procedual Programming

→ Limitations

Object-Oriented (OO) Programming

→ encapsulation:

→ information hiding

→ reusability

→ inheritance (create new classes in term of existing classes, reusability)

→ polymorphism

Classes and Objects

→ Classes

  • user-defined data-type
  • blueprint from which objects are created
  • has attributes (data)
  • has methods (functions)
  • examples: Image, Account, std::vector, std::string

→ Objects

// example
int high_scores;
int low_scores;

// int as an class, high_score as an obejct created from the class
Account frank_account;
Account jim_account;

std::vector<int> scores;
std::string_name;

class Account {
	// attributes
	std:: string name;
	double balance;

	// methods
	bool withdraw(double amount);
	bool deposit(double amount);
}

// creating objects
Account frank_account;
Account jim_account;

Account *mary_account = new Account(); // declared on the heap
	delete mary_account; //delete after using

// creating objects
Account frank_account;
Account jim_account;

Account accounts[] {frank_account, jim_account};

std:vector<Account> accounts1 {frank_account};
accounts1.push_back(jim_account);


// example

// class def. should be upper Case!
class Player {
	// attributes
	string name;
	int health;
	int xp;

	// methods

}


// Define Objects
// example 2
// also Pointer can be adressed to Objects
#invlude <iostream>
#include <string>
#include <vector>

using namespace std;

class Player {
	// attributes (Data)
	string name;
	int health = 100;
	int xp = 3;

	// methods
	void talk(string);
	bool is dead();
};

class Account {
	// attributes
	string name = "Account";
	double balance = 0.0;

	//methods
	bool deposit(double);
	bool withdraw(double);
};

int main() {
	Account frank_account;
	Account jim_account;
	
  // declared two obejcts
	Player frank;
	Player hero;

  // objects usable as any other variable
	// array of objets
	Player players[] = {frank, hero};
	// vector
	vector<Player> player_vec{frank};
	player_vec.push_back{hero};

	Player *enemy = nullptr;
	// new object with address addresses to the pointer
	enemy = new Player;

	delete enemy

	return 0;
}

Accessing Class Members

→ access to: attributes, methods

→ we need an object to access instance variables

using dot operator:

Account frank_account;

frank_account.balance;
frank_account.deposit(1000.00);

if we have a pointer to a object:

// dereference the pointer than use the dot operator

Account *frank_account = new Account();

(*frank_account).balance;
(*frank_account).deposit(1000.00);

// or use the member of pointer operator (arrow operator)

Account *frank_account = new Account();

frank_account->balance;
frank_account->deposit(1000.00);



// example
class Player {
public:
	// attributes
	string name;
	int health;
	int xp;

	// methods
	// attributes usable!
	void talk(string text_to_say) {cout << name << "says " << text_to_say << endl; };
	bool is_dead();
};

int main() {
	Player frank;
	// set attributes via dot operator
	frank.name = "Frank";
	frank.health = 100;
	frank.xp = 12;
	frank.talk("Hi there");

	// enemy as a pointer to object
	Player *enemy = new Player
	// dereference the pointer to get the object
	(*enemy).name = "Enemy";
	(*enemy).health = 100;
	enemy->xp = 15;

	enemy->talk("I will destroy you!");

	return 0;
}

public and private

→ control access of variables and data

Public: accessible everywhere

Private: accessible only by members or friends of the class, not outside the class

Protected: used with inheritance

//public - often used for data that shoudn't be changed
class Class_Name
{
public:

  // declaration(s);

};

//privat
class Class_Name
{
private:

  // declaration(s);

};

//protected
class Class_Name
{
protected:

  // declaration(s);

};


// example - declaring a class
class Player
{
private:
	std::string name;
	int health;
	int xp;
public:
	void talk(std::string text_to_say);
	bool is dead();
};

//access
Player frank; // new object
frank.name = "Frank"; // compiler error
frank.health = 1000; // compiler error
frank.talk("Ready to battle"); // Ok

Player *enemy = new Player();
enemy->xp = 100; // compiler error
enemy->talk("I will hunt you down"); // Ok
delete enemy;

Member Methods

→ member methods have access to member attributes (don’t need to pass arguments)

→ can be implemented inside class declaration: implicitly inline

→ -II- or outside the class declaration: “Class_name::method_name”

→ separation of specification and implementation through external files

Implementation of Member Methods

→ inside class declaration:

// inside class declaration
class Account {
	private:
		double balance;
	public:
	// private Object inside the class declaration accessable
		void set_balance(double bal) {
			balance = bal;
	}
		double get_balance() {
			return balance;
	}
};

→ outside the class declaration:

// outside the class declaration
class Account {
		double balance;
public:
		void set_balance(double bal);
		double get_balance();
};

		// used class has to be named
void Account::set_balance(double bal) {
	balance = bal;
}
double Account::get_balance() {
	return balance;
}

→ seperating Specification from implementation:

// seperating Specification from implementation
// file: Account.h

// Block Guard
// include guard: the included file will only be used once
// Include guards
#ifndef _ACCOUNT_H_
#define _ACCOUNT_H_   // if file is seen, it won't be processed

	// Account class declaration

  class Account {
	private:
		double balance;
	public:
		void set_balance(double bal);
		double get_balance();
	};

#endif

// -> Account.cpp file:
#include "Account.h" //include external header files

void Account::set_balance(double bal) {
		balance = bal;
}
double Account::get_balance() {
		return balance;
}

// use of the .h-file

#include <iostream> // used to include system header files
#include "Account.h"

int main () {
		Account frank_account;
		frank_account.set_balance(1000.00);
		double bal = frank_account.get_balance();

		std::cout << bal << std::endl; // 1000
		return 0;
}

Constructors

→ special member method, invoked during object creation

→ used for initialization, same name as class

→ no return type specified, can be overloaded

// player constructors

class Player
{
private:
	std::string name;
	int health;
	int xp;
public:
	// Overloaded Constructors
	Player();
	Player(std::string name);
	Player(std::string name, int health, int xp);
};

// When defining constructors with no args, default constructor will be called
Player frank;
Player *enemy = new Player; // pointer objects will be generated on the heap
delete enemy;

// using default constructor
class Account
{
private:
	std::string name;
	double balance;
public;
	bool withdraw(double amount);
	bool deposit(double amount);
};
// or default user defined no arg constructor
class Account
{
private:
	std::string name;
	double balance;
public:
	Account(std::string name_val, double bal) {
		name = name_val;
		balance = bal;
	}
bool withdraw(double amount);
bool deposit(double amount);
};
// no std constructor is possible anymore

Account frank_account; // Error
Account jim_account; // Error

Account *mary_account = new Account; // Error
delete mary_account;

Account bill_account {"Bill", 15000.0}; // Ok

Destructors

→ same name as class, proceeded with a tilde ()

→ special member methode, invoked automatically when object is destroyed (release of memory)

→ no return type

→ one destructor per class allowed, can’t be overloaded

// when no constructor and destructor is specified, empt ones are automatically created
class Player
{
private:
	std::string name;
	int health;
	int xp;
public;
	Player();
	Player(std::string name);
	Player(std::string name, int health, int xp);
	// Destructor
	~Player(); //same name as the class
	// will be called automatically when a local object goes out of scope or by deleting the pointer to the object 
};

Overloading Constructors

→ classes can have as many constructors as necessary

→ each must have a unique signature

class Player
{
private:
	std::string name;
	int health;
	int xp;
public:
	// Overloaded Constructors
	Player();
	Player(std::string name_val);
	Player(std::string name_val, int health_val; int xp_val);
};

// implement the constructors
// if attributes not initialized properly, they will contain garbage data
Player::Player() {
	name = "None";
	health = 0;
	xp = 0;
}

Player::Player(std::string name_val) {
	name = name_val;
	health = 0;
	xp = 0;
}

Player::Player(std::string name_val, int health_val, int xp_val) {
	name = name_val;
	health = health_val;
	xp = xp_val;
}

// creating objects
Player empty; // None, 0, 0
Player hero{"Hero"} //Hero, 0, 0
Player villain {"Villain"}; // Villain, 0, 0

Player frank {"Frank", 100, 4}; // Frank, 100, 4

Player *enemy = new Player("Enemy", 1000, 0); // Enemy, 1000, 0
delete enemy;

Constructor Initialization list

→ more efficient way of defining member values

→ immediately follows after parameter list

→ inits data members as object is created

(order of inits is order of declaration in the class)

class Player
{
private:
	std::string name;
	int health;
	int xp;
public:
	// Overloaded Constructions
	Player();
	Player(std::string name_val);
	Player(std::string name_val, int health_val, int xp_val);
};

// PREVIOUS: assignment initialization
Player::Player() {
	name = "None"; //assignment not initialization
	health = 0;
	xp = 0;
} // in this way a object gets created an than overwritten -> very inefficient

// NOW: Initialization List
Player::Player()
	: name{"None"}, health{0}, xp{0} { // before body is called
}

// PREVIOUS: assignment initialization
Player::Player(std::string name_val, int health_val, int xp_val) {
	name = name_val; // assignment not initialization
	health = health_val;
	xp = xp_val;
}

// NOW: Initialization List
Player::Player(std::string name_val, int health_val, int xp_val)
	: name{name_val}, health{health_val}, xp{xp_val} {
}

Delegating Constructors

→ code for one constructor can call another initialization

→ avoids duplicating code, which could lead to errors

(not possible in the body, argument call of other assignment initialization only possible within initialization list) {}

// Before
Player::Player()
	: name{"None"}, health{0}, xp{0} {
}

Player::Player(std::string name_val)
	: name{name_val}, health{0}, xp{0} {
}

Player::Player(std::string name_val, int health_val, int xp_val)
	: name{name_val}, health{health_val}, xp{xp_val} {
}

// Delegating method
Player::Player(std::string name_val, int health_val, int xp_val)
	: name{name_val}, health{health_val}, xp{xp_val} {
}

Player::Player()
	: Player {"None", 0, 0} { // delegating args
}

Player::Player(std::string name_val)
	: Player { name_val, 0, 0} {
}

Default Constructor Parameters

// class definition
class Player
{
private:
	std::string name;
	int health;
	int xp;
public;
	// constructor with default parameter values
	Player(std::string name_val = "None", int health_val = 0, int xp_val = 0);
};

// object definition, overload constructor
Player::Player(std::string name_val, int health_val, int xp_val)
	: name {name_val}, health {health_val}, xp {xp_val} {
}

Player empty; // None, 0, 0
Player frank{"Frank"}; // Frank, 0, 0
Player villain {"Villain", 100, 55}; // Villain, 100, 55
Player hero {"Hero", 100}; // Hero, 100, 0

Copy constructor

→ for a copy, the new item must be created from a existing object

when a copy is made?:
→ passing obejct by value as parameter

→ returning an object from a function by value

→ constructing one object on another of the same class

if not defined manually, a default copy constructor will be used

// declaring copy constructor
// use of same names

// example
Player::Player(const Player &source) // pointer to parameter
	: name{source.name}, // implementation
		health {source.health},
		xp {source.xp} {
}

// example 2
Player::Player(std::string name_val, int health_val, int xp_val)
	: name{name_val}, health{health_val},xp{xp_val} {
	cout << "Three-args constructor for" + name << endl;
}

Player::Player(const Player &source)
	: name(source.name),health(source.health),xp(source.xp) {
	cout << "Copy constructor - made copy of: " << soruce.name << endl;
}

void display_player(Player p) {
	cout << "Name: " << p.get_name() << endl;
	cout << "Health: " << p.get_health() << endl;
	cout << "XP: " << p.get_xp() << endl;
}

int main() {
	Player empty;
	
	display_player(empty);

	Player frank {"Frank"};
	Player hero {"Hero", 100};
	Player villain {"Villain",100,55};

	return 0;
}

shallow copying of constructor

class Shallow {
private:
	int *data;
public:
// methods
	void set_data_value(int d) { *data = d; }
	int get_data_value() { return *data; }

// initialization of constructor
// Constructor
Shallow(int d);
// Copy Constructor
Shallow(const Shallow &source);
// Destructor
~Shallow();
};

// implementation of constructor
Shallow::Shallow(int d) {
	data = new int; // allocate on heap
	*data = d;
}

Shallow::Shallow(const Shallow &source) // a copy of what the data pointer, pointing to the same data
	:data(source.data) {
		cout << "Copy constructor - shallow copy" << endl;
}
	
Shallow::~Shallow() {
	delete data;
	cout << "Destructor freeing data" << endl;
} // delete also data which the copy pointer is pointing to

void display_shallow(Shallow s) {
	cout << s.get_data_value() << endl;
}

int main() {
	Shallow obj1{100};
	display_shallow(obj1);

	Shallow obj2{obj1}; // changes also data of obj1, cause both point to the same data
	obj2.set_data_value(1000);
	return 0;
} // crash

deep copying of constructor

→ not only copy the pointer

→ also copies the data the pointer is pointing to

→ use of deep copy when a raw pointer as a class data member

#include <iostream>
using namespace std;

class Depot {
    private: // Attributes
        int *val; // deref pointer to defined data
    public: // Methods and Prototypes
        void set_new_val(int input) { *val = input; }
        int get_val() { return *val;}
        Depot(int input);
        Depot(const Depot &source);
        ~Depot();
};
    // Implementation

    // Constructor - with Initialization List of overloaded Constructor: Creating Object
    Depot::Depot(int input) {
            val = new int; // Storage Allocation
            *val = input;
        }

    // Copy Constructor
    Depot::Depot(Depot const &source) 
        : Depot {*source.val} {
        cout << "Copy Constructor called" << endl;
    }

    // Destructor
    Depot::~Depot() {
        delete val; // free allocated storage on the heap
        cout << "Destructor called" << endl;
    }

    // Function Call
    void display_val(Depot i) {
        cout << "Value: " << i.get_val() << endl;
    }


int main() {
    Depot obj(42);
    display_val(obj);
    return 0;
}

Move Constructor

→ only L values, which can be addressed

→ move semantics for R-Values, which can be defined to L-Values

→ sometimes movement more efficient than making a copy

// examples references

int x {100}
	int &l_ref = x; // l-value reference
	l_ref = 10; // change x to 10

	int &&r_ref = 200; // r-value ref
	r_ref = 300; // change r_ref to 300

	int &&x_ref = x // compiler error (l value to r-value ref)

// function example

int x {100}; // x is an l-value

// overloading required for passing R- and L-Values
void func(int &num); // A
void func(int &&num); // B

func(x); // calls A - x is an l-value
func(200); // calls B - 200 is an r-value

instead of making a copy of the move constructor (moves the source)

→ making copy of the address of the resource from the source to current object

→ nulls out the pointer in the source pointer

// syntax r-value reference constructor
Type::Type(Type &&source);

Player::Player(Player &&source);

Move::Move(Move &&source);

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

class Move {
    private:
        int *data; // raw pointer
    public:
        void set_data_value(int d) { *data = d; }
        int get_data_value() { return *data; }
        Move(int d); // Constructor
        Move(const Move &source); // Copy Constructor
        Move(Move &&source); // >> Move Constructor
        ~Move(); // Destructor
};

Move::Move(int d) {
	data = new int;
	*data = d;
	cout << "Constructor for: " << d << endl;
}

// inefficient copying
// Copy Ctor
Move::Move(const Move &source) {
    data = new int;
    *data = *source.data;
	cout << "Copy constructor - deep copy for: " << *data << endl;
} // allocate storage and copy

// Move ctor
// >>> not making deep copy, storage efficient
Move::Move(Move &&source)
    : data{source.data} {
        source.data = nullptr;
		cout << "Move constructor - moving resource: " << *data << endl;
    } // steal the data then null out the source pointer

Move::~Move() {
	if (data != nullptr) {
		cout << "Destructor freeing data for: " << data << endl;
} else {
	cout << "Destructor freeing data for nullptr" << endl;
}
	delete data;
}

int main() {
	vector<Move> vec;

vec.push_back(Move{10}); // creating temporary unnamed values (R-values)
vec.push_back(Move{20});
// copy constructors will be called to copy the temps
return 0;
}

this pointer (keyword)

→ contains the address of the object (pointer to the object)

→ can only be used in class scope, all member access is done via this pointer

used for:

// example

void Account::set_balance(double bal) {
	balance = bal; // this->balance is implied
}

// -> to disambiguate identifier use
void Account::set_balance(double balance) {
	balance = balance; // which balance? The parameter
}

void Account::set_balance(double balance) {
	this->balance = balance; // Unambiguous
}

// sometimes useful to know if obejcts are the same: performance issues
int Account::compare_balance(const Account &other) {
	if (this == &other)
		std::cout << "The same objects" << std::endl;
	...
}
frank_account.compare_balance(frank_account);

using const with classe

→ pass arguments to class member methods as const

→ also const objects createtable

const Player villain {"Villain, 100, 55"};
villain.set_name("Nice guy"); // Error, const attribute to changing method
std::cout << villain.get_name() << std::endl; // Error

// example

const Player villain {"Villain", 100, 55};

void display_player_name(const Player &p) {
	cout << p.get_name() << endl; // compiler is not expecting const value for pointer input
}

display_player_name(villain); // error

// -> specific methods will nocht modify the object
class Player {
	private:
	...
	public:
	std::string get_name() const;
	...
};

Static class members

→ class data members can be declared as static

→ single data member that belongs to the class, not the object

→ used to store class-wide information

→ can be called using the class name

class Player {
private:
	static int num_players;

public:
	static int get_num_players();
	...
};

// // //
// in Player.cpp file for the class
#include "Player.h"

int Player::num_players = 0;
// // //

// implement static method
int Player::get_num_players() {
	return num_players;
}

// update the constructor
Player::Player(std::string name_val, int health_val, int xp_val)
	:name{name_val}, health{health_val}, xp{xp_val} {
		++num_players;
}

// Destructor
Player::~Player() {
	--num_players;
}

// main.cpp
void display_active_player() {
	cout << "Active players: "
			 << Player::get_num_players() << endl;
}

int main() {
	display_active_players();

	Player obj1 {"Frank"};
	display_active_players();
	...

}

Structs

→ same as class, expect members are public by default

→ use struct for passive objects with public access

→ don’t declare methods in struct

class

→ use for active objects with private access

→ implement getters/setters as needed

→ implement member methods as needed

Friends of a class

→ function or class that has access to private class member

→ (NOT a member of the class it is accessing)

Friend Functions:

→ can be regular non-member functions

→ can be member methods of another class

Class:

→ another class can have access to private class members

→ Friendship must be granted NOT taken

// non-member function
class Player {
	friend void display_player(Player &p);
	std::string name;
	int health;
	int xp;
public:
	...
};
...
void display_player(Player &p) {
	std::cout << p.name << std::endl;
	std::cout << p.health << std::endl;
	std::cout << p.xp << std::endl;
} // may also change private members


// member function of another class
class Player {
	friend void Other_class::display_player(Player &p);
	std::string name;
	int health;
	int xp;
public:
	...
};

class Other_class {
	...
public:
	void display_player(Player &p){
			std::cout << p.name << std::endl;
			std::cout << p.health << std::endl;
			std::cout << p.xp << std::endl;
	}
};

// another class as a friend
class Player {
	friend class Other_class;
	std::string name;
	int health;
	int xp;
public:
	...
};