0% found this document useful (0 votes)
40 views31 pages

CS202 Study Notes

The document outlines fundamental concepts of Object-Oriented Programming (OOP) including the SOLID principles, the four horsemen of OOP (Encapsulation, Abstraction, Inheritance, Polymorphism), and various aspects of class design. It explains the importance of constructors, destructors, operator overloading, and inheritance types, along with practical examples. Additionally, it discusses the significance of member functions and the redefinition of functions in derived classes.
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
40 views31 pages

CS202 Study Notes

The document outlines fundamental concepts of Object-Oriented Programming (OOP) including the SOLID principles, the four horsemen of OOP (Encapsulation, Abstraction, Inheritance, Polymorphism), and various aspects of class design. It explains the importance of constructors, destructors, operator overloading, and inheritance types, along with practical examples. Additionally, it discusses the significance of member functions and the redefinition of functions in derived classes.
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 31

CS202

Basic definitions and concepts of OOP


SOLID:
1. Single-responsibility principle: each class only has one responsibility (a Calculator
class should only perform calculations, not showing those calculations to the users).
2. Open-closed principle: each class should be open to extensions but closed to
modifications.
3. Liskov substitution principle: objects of each class should be interchangeable with
objects of its subclasses without affecting behavior.
4. Interface segregation principle: clients should not be dependable on interface they do
not use (struct 2DShape should not be depend on FindVolume function)
5. Dependency inversion principle: High-level code should only depend on abstractions
(interfaces), which are implemented in concrete low-level classes (depending also on
these abstractions).

The four horsemen of OOP:


1. Object: an abstraction of a concept or “thing” in real life. Objects contain attributes
(what it is) and behaviors (what it can do). Inside the code, objects are specific
instances of the class they belong to.

1 Tank Sherman {
2 };
3 Tank T34 {
4 };

2. Class: an abstraction of objects. Objects of similar attributes and operations will be


collected into a class.

1 class Tank {
2 private:
3 int caliber;
4 int speed;
5 bool isLoaded;
6 float armorThickness;
7 public:
8 void fire();
9 void move();
10 void stop();
11 void angling();
12 };

The 4 horsemen
1. Encapsulation: is the combination of related attributes and behaviors in the same class
and restricting external code’s access to that class's public interface only. In other words,
through data hiding (the hiding of information concerning the internal workings of
the class, or that which the client code does not need to know), they are unable to
modify or view the internal workings of the object, which allows greater security and
simplification.

1 Tank Sherman {
2 //all attributes and behaviors of the tank is included in this object
3 //users don't need to know information the caliber, speed, or armor
4 caliber = 75; speed = 0; isLoaded = yes; armorThickness = 50.0;
5 // they just need the tank to move, fire, and stop or angling
6 public:
7 void fire();
8 void move();
9 void stop();
10 void angling();
11 };

1. Abstraction: the class provides as much information as the interacting users need for
their use case; irrelevant information are hidden for the sake of simplicity and
performance.

1 class Tank {
2 //we don't need the following data and operations for example, when we
only the tank for basic combat operations
3 int wheelNumbers;
4 string serialNumber;
5 void refuel();
6 };

1. Inheritance: subclasses can be made as extensions to present classes, which includes


new operations/data, without having to redefine everything from scratch.

1 class MBT : Tank {


2 //everything included in Tank
3 //new data
4 bool isThermoOn;
5 bool isFireControlActivated;
6 bool isERAInstalled;
7 public:
8 //everything included in Tank
9 //new operations
10 void ConnectToOtherTanks();
11 void InstallCamflg();
12 };

1. Polymorphism: different objects execute their own operations in response to the same
message (function calls), ensuring agreement across systems.

1 Battleship USSIowa {
2 //fire its whole battery of 406 mm guns
3 void fire();
4 };
5 Tank PzIV {
6 //fire its only 75 mm main gun
7 void fire();
8 };
9 int main() {
10 //init objects
11 USSIowa.fire();
12 PzIV.fire();
13 return 0;
14 }

Side Note: Unified model (OO methodology) of software development


OO design
Identify classes then behaviors:

Classes: 3 paradigms
Abbott and Booch: identify pronouns/noun/noun phrases from the domain text.
Coad and Yourdon: identify separate “things” in the domain text.
Ross: identify against common object categories

=> To use all approaches, first identify from domain text noun/pronoun using Abbott and
Booch, then filter through Coad and Yourdon to remove abstract nouns (system, process,
.etc.), finally checking against Ross for inclusion/exclusion of objects. Class must be:

Modeled after irl things (employees are real)


Is relevant to the context of the program (employee management only considers
employee objs)
Has appropriate scope and understandable attr. and bhv. (employee objects doesn’t
need to have info about their hobbies; external code can directly access relevant
methods like work() or rest() without much complications)

Behavior
Determine whether:

Behavior can be implemented with one class, or multiple ones.


Behavior is static (always happening), or dynamic (will happen under specific
circumstances; also check if invoking the behavior in these circumstances is legal or
not).

Objects
Specific instances of the class with the same attr. and bhv. as the class itself.
Taxonomy of member functions
Constructor/Destructor: init/destroy an instance of the class
Mutator: change certain attr. of an object
Observer: read certain attr. of an object
Iterator: sequentially process all components of an obj (an iterator in a linked list obj
provides the ability for traversing the list)

Constructor/Destructor
Constructor is a special type of function
Same name as class name
- auto. invoked
- no return data type

Difference between assignment and initialization


Initialization is setting values for members when they are first created. Assignment is setting
new values for already-initialized members. It is generally recommend to use initialization
lists for performance and safety reasons (ensuring all member are initialized before
constructor body is executed).

Assignment:

1 Date (int day, int month) {


2 this.day = day;
3 this.month = month;
4 }
5

Initialization:

1 Date (int day, int month) : day(day), month(month) {}

https://www.geeksforgeeks.org/copy-constructor-in-cpp/ (Copy constructor - shallow and


deep copy) (Copy constructor is used to init new objects, while assignment operator is used
to assign to existing objects)
(bitwise copy -> trouble when copying pointer/dynamic memory) -> specify behavior when
copying pointers/dynamic mem.

(clean up memory first of LHS first, then copy the RHS over - overloading = operator)

Operator overloading
By cpp standards, operators can only perform operations on built-in types.
To overload an operator is to assign its special meanings (i.e. to instruct it how to
perform operations on user-defined types) without changing its original meaning.
In operator overloading, if an operator is overloaded as a member, then it must be a
member of the object on the left side of the operator.
Overload-able operators:

List of operators that cannot be overloaded:


:: (scope resolution), . (call member operator), .* (pointer to member operator):
cannot be defined by the user.
sizeof, typeid, ?: (ternary conditional)
=, -> (member operator of pointer), [], () : cannot be overloaded by non-static
functions.
Syntax: <return type> operator<op>(arg)

Parameters in binary operators


Binary operators actually accept two parameters, but the first one, if declared inside a
class, is implicitly accepted as of the object belonging to that class.

1 Fraction Fraction::operator+(int num) {}


2 //lhs of + is implicitly understood as a Fraction object_

For binary operators, the overload function should return a new object of the type (rather
than directly changing any of the operands) because whatever get assigned the result of
the operator expects a new object, not a modified operand.
Complication happens when the first parameter is not of the class type, for
example,

7 + 1/2

which must be declared (and explicitly with both parameters) in the global scope.
Why? Because C++ searches for the first parameter within the scope of which the
function is declared; if it’s declared in the class space, the compiler cannot match
the int type on the left-hand side of the operator to the existing object, so it would
throw an error.
Declaring in the global scope means that the compiler would search for, and match
the int type on the lhs since it is already a built-in type.
Implementation is simply re-calling the operator overload in the class itself:
Fraction operator+(const int num, const Fraction& frac) {
return frac + num; //just use the class-overloaded order
}

Implementing the << and >> operator (self-


explanatory)
1 _//declaration; syntax: in: (numer, denom); out: numer/denom_
2 friend std::ostream& operator<<(std::ostream &os, const Fraction &frac);
3 friend std::istream& operator>>(std::istream &is, Fraction &frac);
4 ...
5 _//implementation_
6 std::ostream& operator<<(std::ostream &os, const Fraction &frac) {
7 os << frac.numerator << "/" << frac.denominator;
8 return os;
9 }
10 std::istream& operator>>(std::istream &is, Fraction &frac) {
11 char ch;
12 is >> ch >> frac.numerator >> ch >> frac.denominator >> ch;
13 return is;
14 }

Must always be overloaded as global functions; whichever class uses them must make
them friends. Overloaded function must return the object type to handle chaining
in/output (e.g. cout << “frac: “ d << endl ;)

Implementing prefix and postfix operators


1 _//prefix (takes no argument; change then return changed object)_
2 Fraction& operator++() {
3 numerator += denominator;
4 return \*this;
5 }
6 _//postfix(takes one dummy argument; change then return unchanged object)_
7 Fraction operator++(int) {
8 Fraction tmp = \*this; ;
9 numerator += denominator;
10 return tmp;
11 }

Should be implemented as member functions since they directly change the attributes of
the object.
CPP design choice is to implement a dummy int argument to differentiate between the
two functions (since their declarations are identical).
Keyword friend: allow class A to access private and protected members of a class B.
Friend is directional, meaning class B cannot do the same to class A.
Use case: implement cin and cout:
Reason: First, syntax requires the object to be called before ostream (so that it
will be cout << t, instead of t << cout (as called inside the class)). Furthermore,
we cannot inject the ostream class with a new member (u-def class) so the
only choice is to make it a free function.
Implementation:

1 friend std::ostream& operator<<(std::ostream&, T const&)

Subscript operators come in pairs: one for mutable, the other


for const objects.

1
2 // 1) non‐const: allows a\[i\] = ...;
3 T& operator\[\](size_t i) {
4 // optional bounds‐check here
5 return data_\[i\];
6 }
7 <br/>// 2) const: allows use in const contexts, but forbids mutation
8 const T& operator\[\](size_t i) const {
9 // same bounds‐check, but this->data_ is read‐only
10 return data_\[i\];
11 }

Inheritance
Syntax

1 class <derivedClass> : <type_of_inheritance> <baseClass> {}; //types of


inheritance: public, protected, private

Derived class B will have access to public/protected members of A, but no matter the
type of inheritance, private members are inaccessible. Public remains public, protected
remains protected.

Types of inheritance
Public: public/protected of base -> public/protected of derived
Protected (accessible within the class that defines them and the classes that
inherit from them): public/protected of base -> protected of derived
Private (only accessible within the class that defines them): public/protected of
base -> private of derived
Non-inheritance: constructor, destructor, and assignment
operator will not be inherited
The default constructor of the base class will automatically be invoked when an object
of the derived class is created (default constructor of base -> default constructor of
derived). We can change which constructor of the base class is called in the constructor
of the derived class.
The derived class can only specify the immediate base class’s constructor (no
chaining specification).

1 class A {
2 public:
3 A(); //this one is default constructor
4 A(int);
5 ...
6 };
7 class B : public A {
8 public:
9 B (int t) : A(t) { //specify constructor
10 }
11 ...
12 };

Destructor will follow an opposite pattern (default des. of derived -> default des. of
base).
Assignment operator must be reimplemented if it is meant to be used. We must
make sure the base class’ sub-object gets assigned before the derived class’ object
gets assigned.

1
2 class Base {
3 int x_;
4 protected:
5 double y_;
6 public:
7 Base(int x=0,double y=0): x_(x),y_(y) {}
8 Base& operator=(Base const& o) {
9 if(this!=&o) {
10 x_= o.x_;
11 y_= o.y_;
12 }
13 return \*this;
14 }
15 };
16 <br/>class Derived : public Base {
17 std::string name_;
18 public:
19 Derived(int x=0,double y=0,std::string n="")
20 : Base(x,y), name_(std::move(n))
21 {}
22 <br/>Derived& operator=(Derived const& o) {
23 if(this!=&o) {
24 // 1) assign the Base part
25 Base::operator=(o);
26 <br/>// 2) assign the Derived part
27 name_= o.name_;
28 }
29 return \*this;
30 }
31 };

Redefinition of member functions


To specify which member function is used in the derived class; note that this may hide other
overloading members in the base class. We have to either call the method in the class it’s
declared or to use the using keyword, which states explicitly that methods from a parent
class inside the derived class is available for use (so when a method of the derived class is
called with parameters that only the parent class supports, the program can still handle this
call).

Example 1:

1 class A {
2 public:
3 ...
4 int a();
5 int a(int x, int y);
6 int a(int x, int y, int z);
7 ...
8 };
9 class B : public A {
10 public:
11 //method 1
12 int a(int x, int y); //choose the 2-arg member to use
13 //method 2
14 using A::a; //all members from A will be loaded to B
15 int a (int x, int y); //B can choose which one to override
16 ...
17 };

Example 2
1 class Base {
2 protected:
3 char\* s;
4 int size;
5 float f;
6 public:
7 Base();
8 Base(char\* s);
9 Base(char\* s, int size);
10 void doSth(float f){
11 cout << "base (float)" << endl;
12 cout << f << endl;
13 }
14 };
15 class MyString : public Base {
16 public:
17 MyString() = default;
18 _//specify the constructor you want to run_
19 MyString(const MyString& string) : Base(string) {
20 s = new char\[string.size + 1\];
21 strcpy(s, string.s);
22 }
23 _//if a MyString object calls doSth, even with a float arg, it will
implement this method instead_
24 void doSth(int t) {
25 cout << "derived (int)" << endl;
26 cout << t << endl;
27 }
28 _//to avoid this, state explicitly that you would use the base class's
method too_
29 using Base::doSth;
30 };
31 int main() {
32 MyString x;
33 float a = 3.2;
34 int b = 5;
35 x.Base::doSth(a); _//1. call the correct class, explicitly_
36 x.doSth(a); _//declare using in derived class, then call normally_
37 x.doSth(b);
38 return 0;
39 }

Pointers and inheritance


A pointer of the base class can point to an object of the derived class (but not the other
way around).

1 Animal* pA;
2 Cat pC;
3 Animal A;
4 Cat C;
5 pA = &C; _//ok_
6 pC = &A; _//not ok_

If the base class and the derived class have the same function, a base-class pointer to
the derived class will always call the base class’s function by default (static binding). For
the pointer of the base class to call member functions of the derived class, implement
that version of the function in the base class as virtual (dynamic binding). (will learn more
in polymorphism)

Multiple inheritance and virtual class


In C++, a class can inherit from many parent classes. This process, however, can lead to
ambiguities such as the Diamond problem, where multiple instances of the same
function are inherited in the same class, leading to compile time error.

To resolve this, virtual class is used. Parent 1 and 2 are called with the virtual keyword,
meaning they share the same copy of the base class, preventing duplicate methods
being passed onto the child class.
Side note: override keyword (in derived class’s function): this is used in conjunction
with virtual as a safety check for the compiler to know if the overriding function
matches the signature (return type and parameters) of its virtual counterpart.
Virtual functions are also available: when declared as virtual in the base class, a
function can be overridden by its version in derived classes when a pointer or
reference to the base class calls points to the derived class instead.

1 class A {
2 void types();
3 ...
4 };
5 _//All following classes B and C share a copy of class A's types()
function._
6 _//Class A is now a virtual base class._
7 class B : virtual public A {
8 };
9 class C : virtual public B {
10 };
11 _//D only has one class A as base_
12 class D : public B, public C {
13 };
14 int main () {
15 D d;
16 d.types(); _//no conflict arises_
17 return 0;
18 }

Additionally, the using keyword can be used to specify which version of the function is
called within the derived class.

1 class B {
2 public:
3 void birds(int birds);
4 };
5 class C {
6 public:
7 void birds(char bird);
8 };
9 class D : public B, public C {
10
11 _//explicitly mention the use of birds function from both classes_
12 using B::birds;
13 using C::birds;
14 void birds(float birds);
15 void printBirds (D& d) {
16 d.birds(10); _//call birds in class B_
17 d.birds("D"); _//call birds in class C_
18 d.birds(2.3); _//call birds in class D_
19 }
20 };

Pure virtual function: a pure virtual function which is stated to not have any
implementation in its base class. Pure virtual functions must be overridden in
subclasses. Syntax:
1 virtual returnType functionName(param1, param 2) = 0;

1. Use case: when we want to enforce override in subclasses (e.g. when behavior of each
specific subclass does not share any commonality).
2. Any class that has at least one pure virtual function becomes an abstract class. Abstract
classes are simply interfaces and cannot be instantiated directly, instead they can only
provide pointers or references to their derived class. Example:

1 _//assuming Base class is abstract_


2 Base* deriv = new Derived(); _//works fine_
3 Base* base = new Base(); _//throws error_

1. If we do not override the pure virtual function in the derived class, then the
derived class also becomes an abstract class.

Encapsulation
Benefits
Simplify usage of the class by hiding the implementation

1 class Tank {
2 ...
3 int remainingShells;
4 void manualReload();
5 public:
6 void fire();
7 ...
8 };
9 int main () {
10 Tank pz;
11 pz.fire(); _//user only need to know the interface to use fire()_
12 return 0;
13 }

Allow class invariants (conditions that must stay true in order


not to break the implementation) to be maintained

1 class Tank {
2 ...
3 int remainingShells = 1;
4 void manualReload();
5 public:
6 void fire();
7 bool checkAmmo();
8 ...
9 };
10 int main () {
11 Tank pz;
12 if (pz.checkAmmo()) _//user cannot directly alter invariant_
13 pz.fire(); _//user only need to know the interface to use fire()_
14 return 0;
15 }

Allow faster debugging and error detection

1 class Tank {
2 ...
3 int remainingShells;
4 void manualReload() {isLoaded = true; remainingShells += 1;} _//wrong_
5 public:
6 void fire() {manualReload(); ...}
7 int checkShells {return remainingShells;}
8 ...
9 };
10 int main () {
11 Tank pz;
12 int rem = pz.checkShells();
13 pz.fire(); _//user only need to know the interface to use fire()_
14 rem = pz.checkShells();
15 _//easy to detect bug (manualReload is the only function modifying
remainingShells)_
16 return 0;
17 }

Allows changing implementation without breaking


logic/rewriting messages

1 class Tank {
2 ...
3 int remainingShells;
4 void autoReload() { //changed implementation
5 isLoaded = true;
6 while (remainingShells > 0)
7 remainingShells -= 1;
8 }
9 public:
10 void fire() {autoReload(); ...} //only need to change interface
11 int checkShells {return remainingShells;}
12 ...
13 };
14 int main () {
15 Tank pz;
16 pz.fire(); //users access interface like before
17 return 0;
18 }

Convention
Use non-member functions if they can be implemented as such (this helps
streamline the class and strengthen encapsulation) (Java and C# could never)
Declare interface before implementation (especially when cooperating
because interface is the main interest) in the order: public - protected -
private.

Polymorphism
Review:
Polymorphism is the use of one interface to represent multiple different data types. It is
closely related to inheritance and typically dynamically implemented with virtual functions
(see virtual function section again). There are two types of polymorphism:

Compile-time polymorphism (Early/static binding): the


program decides which functions to call during compilation

1 int add(int x, int y);


2 int add(char x, char y);
3 ... _//in main_
4 add("d", "g"); _//compiler chooses the correct param._

Operator overloading: operators’ behavior on user-defined data types are decided by the
compiler.

1 bool operator==(T other) {


2 return this->mem >= other.mem;
3 }
4 ..._//in main_
5 bool ops = ex1 > ex2;

Runtime polymorphism (Late/Dynamic binding): virtual


functions

1 class test {
2 virtual void sth();
3 };
4 class realshit : test {
5 void sth() override;
6 };
7 ..._//in main_
8 realshit rs;
9 rs.sth(); _//use realshit's function at runtime_

Example: shape-drawing app

1 #include <iostream>
2
3 #include <vector>
4
5 #include <memory>
6
7
8
9 // Simple 2D point
10
11 struct Point {
12
13 int x;
14
15 int y;
16
17 };
18
19
20
21 // Abstract base class for all shapes
22
23 class Shape {
24
25 public:
26
27 Shape() = default;
28
29 virtual ~Shape() = default;
30
31
32
33 // Must be overridden by derived classes
34
35 virtual void draw() = 0;
36
37 virtual void drawLine() = 0;
38
39
40
41 // Common utilities
42
43 void queueShape() {
44
45 std::cout << "[Queue] Shape queued for drawing\n";
46
47 }
48
49
50
51 bool checkBound() {
52
53 // Stub: always returns true
54
55 std::cout << "[Bound] Checking bounds... OK\n";
56
57 return true;
58
59 }
60
61
62
63 void print() {
64
65 std::cout << "[Print] Shape information\n";
66
67 }
68
69 };
70
71
72
73 // Triangle implementation
74
75 class Triangle : public Shape {
76
77 public:
78
79 Triangle(const Point& A, const Point& B, const Point& C)
80
81 : A(A), B(B), C(C) {}
82
83
84
85 void draw() override {
86
87 std::cout << "Drawing Triangle\n";
88
89 drawLine();
90
91 }
92
93
94
95 void drawLine() override {
96
97 std::cout << " Lines: ("
98
99 << A.x << "," << A.y << ") -> ("
100
101 << B.x << "," << B.y << ") -> ("
102
103 << C.x << "," << C.y << ") -> ("
104
105 << A.x << "," << A.y << ")\n";
106
107 }
108
109
110
111 private:
112
113 Point A, B, C;
114
115 };
116
117
118
119 // Rectangle implementation
120
121 class Rectangle : public Shape {
122
123 public:
124
125 Rectangle(int width, int height, const Point& topLeft)
126
127 : width(width), height(height), A(topLeft)
128
129 {
130
131 B = { topLeft.x + width, topLeft.y };
132
133 C = { topLeft.x + width, topLeft.y + height };
134
135 D = { topLeft.x, topLeft.y + height };
136
137 }
138
139
140
141 void draw() override {
142
143 std::cout << "Drawing Rectangle\n";
144
145 drawLine();
146
147 }
148
149
150
151 void drawLine() override {
152
153 std::cout << " Lines: ("
154
155 << A.x << "," << A.y << ") -> ("
156
157 << B.x << "," << B.y << ") -> ("
158
159 << C.x << "," << C.y << ") -> ("
160
161 << D.x << "," << D.y << ") -> ("
162
163 << A.x << "," << A.y << ")\n";
164
165 }
166
167
168
169 private:
170
171 Point A, B, C, D;
172
173 int width, height;
174
175 };
176
177
178
179 // Circle implementation
180
181 class Circle : public Shape {
182
183 public:
184
185 Circle(int radius, const Point& center)
186
187 : radius(radius), center(center) {}
188
189
190
191 void draw() override {
192
193 std::cout << "Drawing Circle\n";
194
195 drawLine();
196
197 }
198
199
200
201 void drawLine() override {
202
203 std::cout << " Center: ("
204
205 << center.x << "," << center.y
206
207 << "), Radius: " << radius << "\n";
208
209 }
210
211
212
213 private:
214
215 Point center;
216
217 int radius;
218
219 };
220
221
222
223 // Singleton window for drawing bounds
224
225 class DrawWindow {
226
227 public:
228
229 static DrawWindow& getInstance(int w = 800, int h = 600) {
230
231 static DrawWindow instance(w, h);
232
233 return instance;
234
235 }
236
237
238
239 int getBoundsX() const { return width; }
240
241 int getBoundsY() const { return height; }
242
243
244
245 private:
246
247 DrawWindow(int w, int h) : width(w), height(h) {}
248
249 DrawWindow(const DrawWindow&) = delete;
250
251 DrawWindow& operator=(const DrawWindow&) = delete;
252
253
254
255 int width, height;
256
257 };
258
259
260
261 // Application managing shapes
262
263 class DrawApp {
264
265 public:
266
267 DrawApp(int width, int height)
268
269 : window(DrawWindow::getInstance(width, height))
270
271 {}
272
273
274
275 void addShape(std::unique_ptr<Shape> shape) {
276
277 shapes.emplace_back(std::move(shape));
278
279 }
280
281
282
283 void renderAll() {
284
285 for (auto& shape : shapes) {
286
287 shape->queueShape();
288
289 if (shape->checkBound()) {
290
291 shape->draw();
292
293 shape->print();
294
295 }
296
297 }
298
299 }
300
301
302
303 private:
304
305 DrawWindow& window;
306
307 std::vector<std::unique_ptr<Shape>> shapes;
308
309 };
310
311
312
313 int main() {
314
315 auto& win = DrawWindow::getInstance(1024, 768);
316
317 DrawApp app(win.getBoundsX(), win.getBoundsY());
318
319
320
321 app.addShape(std::make_unique<Triangle>(Point{0,0}, Point{50,0},
Point{25,50}));
322
323 app.addShape(std::make_unique<Rectangle>(100, 50, Point{10,10}));
324
325 app.addShape(std::make_unique<Circle>(30, Point{200,200}));
326
327
328
329 app.renderAll();
330
331 return 0;
332
333 }

Class template
Definition
To implement the same class/variable/function for different data types.
Syntax: use keywords template and typename/class:

1 _//function_
2 template &lt;typename A, typename B, ...&gt; Returntype Functionname (A a,
B b,...) {}
3
4 _//class_
5 template &lt;class A, class B, ...&gt; {};
6
7 _//variable_
8 template &lt;typename A, typename B, ...&gt; (constexpr) Returntype
Variablename;

Static function
Functions whose scope is limited to its translation unit (cannot be accessed outside its file).
Used to protect functions from unwanted usage/when the function name is meant to be
reused outside the translation unit.

Template metaprogramming
The concept of using template to perform calculations at compile time (using recursion).

Exception handling
Way to catch errors
Terminate program immediately after error: bad practice because users/dev won’t know
where the problem occurs to fix it -> ambiguity. Also in many cases, programs can simply
be designed to handle the exception and continue to run.
Return special value: clunky if-elses if there are multiple cases to handle. Also not
possible if all possible values are expected as results of the program.
Handle but change program state to “error”: clunky to handle, not good when running on
parallel processors (one pro. can terminate early while others are still running)
Use try-throw-catch system: safe, clear, and customizable. How it works (try must always
be followed by catch):
1 void calc (int x, int y) {
2 if (y == 0) throw ("div by zero"); _//the throw call will return a string
if condition match_
3 return x / y;
4 }
5 int main() {
6 int x = 2, y = 0;
7 int res;
8 try { _//probe the program to see if exception occurs_
9 res = calc (x, y);
10 }
11 catch (char* s) { _//catch the return type of throw call_
12 std::cout << "error: " << s << endl;
13 }
14 return 0;
15 }

Three types of catch


Return built-in data types: simplest but not very useful (no desc. of what happened)

1
2 void sth() {
3 bool stuff;
4 ...
5 if (!stuff) throw -1;
6 ...
7 }
8 int main () {
9 try {
10 sth();
11 }
12 catch (int& i) { _//usually gets thrown value by reference_
13 std::cout << i << endl;
14 }
15 return 0;
16 }

Return standard exceptions: a set of classes collected under the header stdexcept ;
1 #include <stdexcept>
2
3 int main () {
4 int arr\[\] = {1, 2, 3};
5 int res;
6 try {
7 res = arr\[4\];
8 }
9 catch (const out_of_range& e) {
10 std::cout << "error: " << e.what();
11 }
12 return 0;
13 }

Throw custom messages/classes (preferably inherited from standard exception classes


to maintain integrity)
There can be multiple catch blocks that “catch” different return types from various throws;
catch(...) is often used as the last block that handles any remaining throw. If after all call
stacks are unwinded and a suitable catch block still hasn’t been found, the program will
simply terminate.

1
2 void sth() {
3 bool stuff;
4 int numstuff;
5 char chrstuff;
6 ...
7 if (!stuff) throw -1;
8 if (numstuff < 0) throw "no val";
9 if (chrstuff == '0') throw -1.0;
10 }
11 int main () {
12 try {
13 sth();
14 }
15 catch (int& i) { _//usually gets thrown value by reference_
16 std::cout << i << endl;
17 }
18 catch (char\* str) {
19 std::cout << "error: " << str << endl;
20 }
21 catch (...) { _//last catch block that would accept any return type_
22 std::cout << "remaining exception" << endl;
23 }
24 return 0;
25 }

throw can throw anything; what it throws is declared after the function

void sth() throw (int i, char c);


void foo(int i) throw(); //throw is not expected to return anything

RAII: Resource Acquisition is Initialization


Holding resource is a class invariant.
Resource allocation happens at initialization (creation) time, resource release happens at
finalization (destruction) time. In other words, resources are held during the lifetime of an
object; resources must be acquisited for initialization to succeed, which guarantees that
resources are available during object lifetime.
-> If there are no object leaks, there are no resource leaks.

1 class file {
2 public:
3 file (const char\* filename):
4 f(std::fopen(filename, "w+")){
5 if (!f)
6 throw std::runtime_error("open failure");
7 }
8 ~file(){
9 if (0 != std::fclose(f))
10 {... } _// handle it_
11 }
12 void write (const char\* str);
13 }

Const
Types of const declarations
Normal pointer points to constant value: this is saying “this pointer promises not to
change the value it points to”.
Syntax: T const* or const T*

1 int x = 10;
2 int y = 5;
3 int const* p = &x; _//this pointer is init to point to value x_
4 p = &y; _//correct: pointer can point to another value_
5 y = 55; _//correct: value can still directly be changed_
6 *p = 6; _//error: cannot change value through pointer_

Constant pointer points to normal value: this is saying “this pointer promises not to
change the address it points to.”
Syntax: T* const

1 int x = 5;
2 int y = 2;
3 int\* const p = &x; _//assign const ptr to a normal val_
4 x = 6; _//correct: val can still be changed_
5 \*p = 3; _//correct: ptr can modify val_
6 p = &y; _//error: ptr cannot point elsewhere_

Constant pointer points to constant value: this is saying “this pointer promises not to
change the address it points to, and this variable promises not to change its value also.”
Syntax: const T* const

1 int x = 5;
2 int y = 7;
3 const int* const p = &x;
4 p = &y; _//error: ptr cannot change adr._
5 *p = 3; _//error: var cannot change val_

Constant methods: non-class functions and class members (commonly getters) can
also be declared as constant.
Non-class funcs. : const return_type func_name ()
Members: return_type func_name() const
Note: specify getter with const return types to avoid unwanted mutation of private
members

1 class doSth {
2 ...
3 public:
4 const int& getNum () const {} _//correct: const function returns a const-
to-reference val._
5 int& getNum() const {} _//error: const function returns a normal val._
6 };
7 int main () {
8 doSth random();
9 random.getNum() = 5; _//this call can modify second declaration, but not
the first_
10 }

Constant objects: can only invoke constant methods (because invoking non-constant
methods would potentially change the object’s state which violates its premise)

1 class dosth {
2 int val;
3 public:
4 dosth (int v) : val(v) {}
5 ...
6 int getVal() const {
7 return val; _//would throw error if try to reassign val in here_
8 }
9 void setVal(int newVal) {
10 val = newVal;
11 }
12 };
13 int main () {
14 dosth nConst (29); _//non-const obj_
15 int res = nConst.getVal(); _//correct_
16 nConst.setVal(30); _//correct_
17 const dosth Const (10); _//const obj_
18 res = Const.getVal(); _//correct_
19 Const.setVal(5); _//error: calling non-const method -> modifying const
obj_
20 return 0;
21 }

Note: Const overloading: there can both be a const and a mutable version of the same
function in a class (as seen in operator overloading)
Keyword mutable: used to declare a var. as mutable, even in a const function

1 class stuff {
2 int a = 0;
3 mutable int b = 1;
4 public:
5 int add(int other) const {
6 return a + other; _//error: a is not mutable_
7 return b + other; _//correct: b is mutable_
8 }
9 };

Design pattern
https://refactoring.guru/design-patterns go here

You might also like