Classes and Objects
Procedual Programming
- focus is on processes or actions that a program takes
- programs are typically a collection of functions
- data is declared separately
- data is passed as arguments into functions
- fairly easy to learn
→ Limitations
- functions need to know the structure of the data, if the structure of the data changes, many functions must be changed
- as programs get larger, they become more: difficult to understand, maintain, debug or extend
Object-Oriented (OO) Programming
- focus on classes that model domain entities
- used successfully in very large programs
→ encapsulation:
- objects contain data AND Operations that work on that data
- abstract data type (ADT)
→ information hiding
- implementation-specific logic can be hidden
- more abstraction
- code to the interface without the need to know the implementation
→ reusability
- easier to reuse classes in other applications
- faster development and higher quality
→ 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
- represent a class from which they are created
- each has its own identity and value
- example: frank’s account is an instance for an Account
// 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:
- access data member and methods
- determine if two objects are same
- can be dereferenced *this
// 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:
...
};