1/23
Looks like no tags are added yet.
Name | Mastery | Learn | Test | Matching | Spaced |
|---|
No study sessions yet.
Constructors
Constructors helps to initialise the object of a class it’s can either accept an argument or not. There can be many constructors in a class (overloaded)
eg: // in BankAccount.h
class BankAccount {
...
public:
BankAccount();
void deposit(double amount);
. . .
};
// in BankAccount.cpp
BankAccount::BankAccount() {
balance_ = 0.0;
owner_ = "";
}
Multiple Constructors: BankAccount::BankAccount() {
balance_ = 0.0;
owner_ = "";
}
BankAccount::BankAccount(string name) {
balance_ = 0.0;
owner_ = name;
}
BankAccount::BankAccount(double v, string name){
balance_ = v;
owner_ = name;
}
Constructors with Arguments
A constructor is Invoked automatically whenever a class instance is created.
A constructor with no argument is called a default constructor. In a constructor each class member has their own default constructor called:
class Bank {
public:
Bank(); // Bank’s constructor
// it invokes BankAccount constructor
// (100 times) before running itself
private:
BankAccount accounts_[100];
};
Members can also be initialised in the initialiser list
BankAccount::BankAccount() :
balance_(0.0), owner_("")
{
// nothing here
}
“new” is actually a two-step process:
• Allocate memory for the object, then
• The constructor of the object is called
• Might be the default constructor
BankAccount bp; //constructor not called
bp = new BankAccount; // constructor called
BankAccount allAccounts = new
BankAccount[10]; // is constructor called?
• You can pass arguments to constructors, too
BankAccount* b1 = new BankAccount("J.
Smith", 5.00);
Destructors
Destructor is a function that is automatically called when a class instance is destroyed
class BankAccount {
public:
BankAccount();
. . .
~BankAccount();
};
Delete is a two step process:
Call destructor of pointed to Object
Then release the memory Occupied by that object
When a constructor is invoked all member objects also have their destructors called (automatically)
Shallow Copy and Deep copying
Means that C++ copies each member of the class individually when the classes are simple (uses this = for copying).
However when the class handles dynamically allocated memory we use Deep copying (it’s allocated memory for the copy and then copies the actual values ).
To allow deep copy to happen automatically we allow copy constructor - called automatically in certain cases where an object must be initialised from an existing object.
If you don’t define your own copy constructor, the complier generates a default copy constructor. To perform a deep copy you need to write your own copy constructors
Possible solution (?): home-made copy() function
• Could use a client function:
void copy(const DynArray& from, DynArray&
to);
• Or a member function:
void DynArray::copy(const DynArray& other);
• Problems:
• Must be called explicitly; rely on user of this class to call it
• There are many scenarios where “hidden” copying is
performed
• By default, C++ performs all such copying using shallow copy
Technicalities of = (THIS)
DynArray a2 = a1; is not the same
DynArray a2;
a2 = a1;
The first case the object is created with copy constructor. In the 2nd case the default constructor is used to create a2 then a1 is copied to the already - existing a2
The latter = is the copy assignment operator
• Overloaded operator=
• DynArray& DynArray::operator=(const
DynArray&);
This - a reserved word in C++ means a pointer to a current object Can be used like any other pointer
DynArray *ap = this;
if (ap == this){ … }
Copy Assignment operator
Copy Assignment operator :
4 Steps: Test for same objects if (&other != this) // ok to do copy
Delete old dynamically allocated data: Call some cleanup_() function, or
• directly: delete [] array_;
copy new data
Return a reference to the current object - return *this;
Ways to Reduce Copy : Complier Optimisation
Move Semantics
Class Relationship
Inheritance is the idea where one class is allowed to inherit the features
Has-a”: Class in a Class
class StudentCouncil
{
Student president_;
Student ministerOfPropaganda_;
Student membersAtLarge_[5];
}; of another class.
Inheritance Terminology
A derived class inherits from a base class by putting
: public BaseClassName in the class
declaration
class Shark : public Fish {
// Shark-specific stuff here
};
// Shark is the derived class or
subclass
// Fish is the base class or superclass
• Shark declares that it “is-a-kind-of” Fish by inheriting
from Fish
Bank Account Inheritance:
class Account {
...
double balance_;
Customer owner_;
Date dataOpened_;
...
void makeDeposit (double amount);
...
};
class SavingsAccount : public Account {
// has all data members that Account has, plus has its own
double interestRate_;
// has all methods that Account has, plus has its own
void creditInterest();
};
Rules of Inheritance
All data and methods in base class are automatically
inherited by derived classes Derived class can add new functions or data Derived class can override base class functions
Subclass can only override functions, not data
members
Public/Private/Protected
Public members of base class: visible to derived
class and clients that use it
• Private members of base class: still not visible to
derived class or clients
• The private members are still there inside the derived
object! They just aren’t visible
• Protected members in base class: visible in derived
class, but not visible to clients
-Colour ColourPoint::getColour()
{
return colour_;
}
void ColourPoint::print()
{
cout << "(" << getX() << ", " << getY()
<< ")/" << colour_;
}
• Can’t use xpos_ and ypos_ directly if declared as
private in base class; hence getX() and getY()
• But ok if they are declared as protected
Inheritance
Use the :: scope resolution operator to explicitly call an
overridden method from the derived class
void ColourPoint::print()
{
Point::print(); // print x and y
cout << ", " << colour_;
}
C++ supports inheriting from multiple base classes
class Student { …} ;
class PhDStudent : public Student { …
};
class Staff { … };
class GTA : public Staff, public
PhDStudent { … } ;
Constructors are not inherited!
Constructor of base class is always called, before
constructor of derived class executes
Substituting
New notation: : baseclass(args, …) calls
base class parameterised constructor
An instance of a derived class can always be substituted for an instance of a base class
Derived class guaranteed to have at least the same data and interface as a base class
If it’s true of a mammal, it’s true of a dog”
class Base { … };
Class Derived : public Base { … };
Base b;
Derived d;
b = d; // ok, but may lose stuff (slicing)
d = b; // not ok
Pointers and Inheritance
Same substitution rules applies to pointers/References :
Base* b = new Derived(); // ok
Derived* d = new Base(); // not ok
Static and Dynamic Types
Every Variable has a Static and Dynamic type.
Static type is declared type of variable.
Dynamic type is type of object the variable actually contains or refers to.
Dispatch
Dispatching is the act of deciding which piece of code to execute when a method is called.
Static dispatch means that the decision is made statically(at compiled time )
Point* p = new ColorPoint(3.14, 2.78,
green);
p->print();
// p is a Point*, so call Point::print()
Dynamic Dispatch - If an overriding function exists, call it. The decision is made at run-time, sometimes called late binding. Supports Polymorphic behaviour
Example of dynamic Dispatch:
class Point { // base class
public:
virtual void print();
…
};
class ColourPoint : public Point { // derived class
public:
void print() override;
…
};
Point *p = new ColorPoint(3.13, 5.66, blue);
p->print(); // calls ColorPoint::print()
The conditions for Dispatch
Dynamic Dispatch only happens under these two conditions:
The objects is accessed through a pointer or reference
The function is Virtual
In any other cases you get static dispatch
Animal* zoo[20]; // array of 20 ptrs
for(Animal* p : zoo)
p->laugh();
• An array of pointers to objects derived from the same
base class
• All the objects pointed to are animals, but some might
be dogs, cats, hedgehogs…
• Each subclass might have its own methods for
behaviour like “scream” “fight” “laugh”…
• If I write zoo[i]->laugh() I want to get the
appropriate behaviour for that type of animal
• Won’t happen unless laugh() is virtual in Animal class
Dynamic Dispatch is slightly slower that static dispatch
Virtual Destructors and Dynamic Cast
Each animal was created with their own constructor.
Destructors can also be made virtual like other functions
Upcasting: convert from derived to base class, ok
• Use static_cast or even done implicitly
Derived dp = …;
Base bp = static_cast<Base*>(dp);
• What about the other direction, downcasting?
Base bp = …;
Derived dp = static_cast<Derived*>(bp);
• Unsafe: undefined behaviour if bp does not really point to
Derived
• Use dynamic_cast for run-time checking:
Derived dp = dynamic_cast<Derived>(bp);
• Returns nullptr if bp is not really a Derived*
Pure Virtual Functions
and Abstract Classes
An abstract Class is one that should not or cannot be instantiated- it only defines an interface.
A concrete Class can have instances Classes are recognised as abstract if they have at least one pure virtual function
A pure virtual function is a not implemented in the base class must be implemented in the derived classes syntax: append "= 0" to base method declaration
class Button {
public:
virtual void clicked() = 0;
};
Compiler guarantees that class with pure virtual
functions cannot be instantiated
• A call to a pure virtual function uses the version from
some derived class
class Exitbutton : public Button {
public:
void clicked(); //with implementation
};
Button *b = new Exitbutton();
b->clicked();s
Overloading
Different Functions can have same name if argument list is different.
To resolve mean to decide which version of the overloaded function is being called. This is determined by matching actual arguments against possible formal arguments
Example of resolving:
Function declarations
void snark(int);
void snark(double);
void snipe(char []);
void snipe(double);
void sneep(char);
void sneep(double);
snark(1); // Integer Snark
snipe(1); // Double Snipe
sneep(1); // Ambiguous
Overloading pt 2
Overriding - same function name and signature in derived class, overrides base class
Overloading - same function name and different signature
In C++ most operators can be overloaded just like functions
A class can designate another class or a standalone function
as a friend
• Grants access to private members to the friend
• Friendships are neither reciprocated nor inherited
class X {
public: …
private: …
friend class Y;
// Y can access X’s private members
friend void f();
// f() (not a member of X) can access X’s
// private members
};
eg
class Colour {
public:
Colour(int r, int g, int b);
// overload as member methods
Colour operator+(const Colour& rhs);
// overload as friend function
friend ostream& operator<<(ostream&, const Colour&
col);
private:
int r_, g_, b_;
};
Colour Colour::operator+(const Colour& rhs)
{
Colour c(*this);
c.r_ += rhs.r_;
c.g_ += rhs.g_;
c.b_ += rhs.b_;
return c;
}
ostream& operator<<(ostream& os, const Colour& col)
{
os << "R=" << col.r_ << ", B=" << col.b_
<< ", G=" << co.g_;
return os;
}
operators
operator functions can be implemented as
• members functions of a class
• or non-member functions, typically as friends
Sometimes implementing as non-members is useful when it
involves implicit conversions
class Rational {
int num; int denom;
…}
Rational operator+(const Rational& lhs, const
Rational& rhs);
• We want to be able to write e.g. 2 + 3/4
• Details omitted; see [Book: C++ for Java programmers Ch.5]
• Stream operators (<<, >>) are typically overloaded as non-
members
Related operators are not “automatically” overloaded
• E.g. if you overload +, it doesn’t mean += will automatically do the right
thing
• Likewise for != (doesn’t follow from ), < (doesn’t follow from >) etc
• Have to be overloaded explicitly and separately
• Although can reuse code, e.g.
bool operator(const Colour& lhs, const Colour&
rhs) {
return (lhs.r_ rhs.r_ &&
lhs.g_ rhs.g_ &&
lhs.b_ == rhs.b_);
}
bool operator!=(const Colour& lhs, const Colour&
rhs) {
return !(lhs == rhs); // call the above
}
The array-indexing operator [] can be overloaded as
well
• Two versions:
Type operator[](int i) const;
// accessor
• (assuming Type is a primitive type)
Type& operator[](int i);
// mutator
• Return by reference so can be used like a[0]=1;
• Overload both; body likely identical.
Streams
Only need to decide if it’s input or output or both
• istream – allows reading from
• ostream – allows writing to
• iostream – allows both
Overloading << is an overloaded operator
ostream& operator<<(ostream& os, const
SomeClass& c)
{
return os << c.toString();
// assuming SomeClass has a
// toString() function
}
• os << x is actually operator<<(os, x)
Input Stream States
Each input stream has a state
• The state is good if every operation succeeds
• The state is bad if some operation failed
• Couldn’t open a file
• Couldn’t read in a value of the expected type
• Couldn’t find delimiting character for getline()
• The stream state is a boolean that we can test
Suppose we try to read an integer, but no integer is there
int age = -1;
cin >> age;
if (!cin)
{
// could not read the age, age still is -1
cout << "The age couldn't be read." << endl;
}
Dealing with Input Stream Errors
Once the input stream fails you can use clear()
to clear the input stream's fail state, but you must
also do something about the input that caused it to
fail; simply trying to read the input again with the
same input statement will cause it to fail again.
int num;
cout << "Enter an integer\n";
while (! (cin >> num) ) {
cin.clear();
cin.ignore(1000,'\n');
// ignore what’s typed until newline
// try without them and see what happens
cout << "Enter an integer\n";
}