CS247

Final Review CS247

A review session Ross held.

Observer: Some classes to react based on changes in other classes.

Example: Spreadsheet.

Subject: Gameboard. Observers: Network class

Subject chagnes ⇒ notify observers called, observers

React: perhaps one class dispatches network request, another renders info to screen.

Template Method Pattern: Maybe we want to contain subclass behaviour

First! display the name of my type Second: If optional, append question mark

private:
	virtual string get.description() = 0;
public:
	string print() {
		string name = get.description();
		if(optional) name += "?";
		return name;
	}

private virtual that can be overridden stuff that cannot be customized is in print() after the get.description() function.

Strategy Pattern: We want one class to use an algorithm that may be changed at runtime. Consider a Poker game: Dealer who owns an abstract shuffle class, providing some method that is able to shuffle. We can have concrete subclasses that will have concrete shuffling. For example: PerfectShuffle that owns shuffle(), MagicianShuffle that also have its unique shuffle() method. One class relying on an interface, abstract class.

(Insert image)

Bridge Pattern: we want one class to use an [implementation] that may be changed at runtime The two are basically the same? But difference is algorithm vs. implementation. Class own some class hat has some implementation (abstract class) with method push_back(). We might have ListImplementation, this one use a list as data structure and does push_back(), and we can have Deque Implementation that also have push_back().

Both have interface and therefore have to instantiate it.

Adapter Pattern: We already have one interface and we want to use those classes with a client that expects a different interface.

Consider an Audio application: Play, stop music. We may use Library1 for an old computer, and Library2 for a new computer. Library1: expects a method called stop. Library2: expects a method called end. We write an adapter. (instead of doing both in one class which is also possible)

Music class provides some method called stop(), and then we can have an adapter class which has a method called end() purpose is to own some Music class and then end() will call m.stop() for Music m. Now: Can pass Music object to Library1 and can pass Adapters to Library2. Benefit: We didn’t have to change public interface of Music class. Purpose of Adapter be something to work for Library2 and Wrap a class which ends up calling the method of some other class.

Music m;
Adapter a{m};
Library1.use(m); // expects stop method?
Library2.use(m); // expects end method

owns or has relationship both works? Music is adaptee

Visitor Pattern: One: Achieve double dispatch (Ability to choose a method based on 2 polymorphic types) ( Usually dispatch is done when we call a function on a pointer, it will be able to override depending on the method if the method is virtual, but can’t do it with argument for instance) Two: Add functionality to a class without changing its public interface.

Implement an accept method on each class in our large hierarchy, taking a Visitor& or Visitor*. And call v.visit(*this) also a polymorphic and let’s you figure out what kind of visitor it is.

Command Pattern: Have classes that represent an ‘action’ or ‘command’ in our program.

UML: Have a superclass Command, have concrete Command1, Command2. They all have some action(). We have some Invoker class that has some number of commands.

Benefit: Each Command can be stored, creates a log of what has happened. For example, we can have History, Undos (each command has can have a reverse), Macros (store a sequence of commands). Useful when we want to add another layer of abstraction, controller creates command and sends to model to execute it. Model can keep track of History, etc.

Difference between Strategy and Command: Strategy is really concentrated on having different algorithm, Commands will not be performing same algorithm.


Smart Pointers and RAII

RAII: Resource Acquisition is Initialization Essentially: Every resource in a program should be wrapped inside of a stack allocated class. Purpose of this is that the

  • Constructor: Acquire the resource.
  • Destructor: Free the resource.

Examples of resources: Dynamic memory, Files

std::ifstream file{"CS247.txt"})

This follows RAII, since constructor acquires the resource.

class ifstream{
	FILE* fp;
	public: 
		ifstream(const string& s){ // constructor
			fp = fopen(s.c_str()); // get file pointer from OS, so we can use
		}
		~ifstream(){
			fclose(fp);
		}
};

Constructor acquires the resource and then destructor will free the resource. We free the file back to the OS whenever the destructor run. Stack allocated ⇒ always runs whether leaving normally, or when an exception is thrown, always runs. (Purpose of putting on stack in RAII)

Unique_ptrs: Principle of RAII applied to dynamic memory. Constructor: Acquire the memory Destructor: Delete the memory

template<typename T> class unique_ptr {
	T* ptr;
	public:
		unique_ptr(T* ptr): ptr{ptr}{} // using MIL to acquire pointer 
		~unique_ptr() {delete ptr;}
		...
		// The big 5, copy is disallowed, move is allowed. 
};

Use of unique_ptr: Model an owns-a relationship.

class Dealer {
	unqiue_ptr<Shuffle> algo; // owns-a relationship
	public:
		void play {
			algo.Shuffle(deck);
		}
};

Benefit: still use polymorphism when dealer destructor runs, it will destruct the algo pointer too. Only use if we have polymorphism.

Owns-a relationship may also be modelled by a simple object field.

Pros of unique_ptr: Allows polymorphism, destructor works by default. Cons: Copying of classes with unique_ptr fields is disabled.

Pros Object field: Destructor works by default, copying works. Cons: No polymorphism

Shuffle is an abstract class, so we want polymorphism.

shared_ptr: Model shared ownership. Many pointers pointing to a single object. If all go out of scope ⇒ object is deleted.

Biquadris: Extra points are given when all of the blocks are deleted. Get points for the red block, but not green block. (Insert image)

4 blocks: Shared_ptr to green piece. When the line is cleared: 3 blocks are deleted. Green piece object remains. When the last block is deleted ⇒ Green piece destructor runs, memory is freed, add points in the destructor.

shared_ptrs use reference counting. Each shared_ptr has two fields (consumes memory): pointer to the object, pointer to the integer storing the shared reference count. → recommend doing shared_ptr, make sure know big 5, template,

When to use make_unique and make_shared? They should always be used over directly calling the constructor: Considered better style

  1. unique_ptr<C> p{new C{...}} Every new should be paired with a delete. Philosophical reason why.
  2. Problem: double delete
C* cp = new C{...};
auto p = shared_ptr<C>(cp);
auto q = shared_ptr<C>(cp);
  • Cause a double delete
  1. Look at notes: obscure ordering of argument evaluation can cause memory leak.
auto p = make_shared<C>(...);
 
shared_ptr<C> q = p; // Invoking the copy constructor, they know they share ref counter
 
unique_ptr<C> r{p.get()}; // cause issues (double delete)

Only copy constructor update the reference count.

Coupling + Cohesion We want: Low coupling + high cohesion. Lowest Coupling: Classes interact with simple results + parameters.

class A{
	...
	void f(){
		int x = b.h(100)
	}
};
 
class B{
	...
}

Low level of coupling.

Higher: Communicating via arrays of structs:

class A {
	void f(){
		Info i = b.h(Info{1, "CS247", 'a'});
	}
};
 
struct Info{
	int x;
	string y;
	char z;
};

Need to worry if we have the info struct has does field.

Higher” Classes sharing globals.

bool flag = false;
 
class A {
	void f(){
		flag = true; // !flag or flag 
	}
};
 
class B {
	void h(){
		if (flag){
			...
		} else {
			...
		}
	}
};

Crap, using global variables, where else is it also used.

Higher: Classes are friends. Have access to the other entire private fields.

class A{
	bool flag = false;
	public:
		void f() {
			if(flag) {
				...
			}
		}
 
		friend class B;
};
 
class B{
	public:
		void h(A& a){
			a.flag = true;
		}
};

B can reach directly into A’s private fields and mess up with those invariants. Think of what is in the class, what’s in the friend class. Worry about invariants.

Cohesion: Low cohesion: An example: <util> Lowly cohesive: module with no relation to others.

Higher: <algorithm> bunch of general algorithm functions, relation is a common theme

Highest: <string> - All parts of the module cooperate to do one thing: provide string functions.

Design pattern: there will be coding of design pattern in the final


Virtual Inheritance Memory Hierarchy

No multiple inheritance: A inherits from B and inherits from C. Memory: V pointer at the start, then we have A fields, B fields and C fields. Easy to figure out where things are in memory.

C* cp;
cout << cp->a;
A* ap = cp
cout << ap->a;

Virtual Inheritance: Consider the deadly diamond

D objects:

  • vptr // this allows to check fields at runtime
  • B fields
  • vptr
  • C fields
  • D fields
  • vptr
  • A fields all vptr points to D vtable

B object:

  • vptr // this allows to check fields at runtime
  • B fields
  • vptr
  • A fields all vptr points to B vtable
B* bp = ...; // B object or a D object
 
cout << bp->a; // This fields location is not knowable at compile-time!

How do we find the A fields depending on what thing we are pointing at, B or D? How does the compiler know how many bytes to go down? bp depends on user input!! How can we execute machine instruction to get both of them. Fundamentally cannot be determined at compile time.

vtable now stores the offsets of fields in our class. (Virtual Inheritance) This is where the name virtual inheritance comes from.

A* ap;
cout << ap->a;

A objects:

  • vptr
  • A fields

However if

C* cp = &d; // If we have &d, then we know it's a D object. Compiler can figure out, we can just check where the C field is located (hardcoded at compile time)