Skip to content

Module 02 Tutorial

Prerequisites: Review Module 02 Concepts first.

Module 02 introduces the Orthodox Canonical Form (OCF) - the four functions every C++ class should have. You’ll also learn fixed-point arithmetic and operator overloading.


Create a Fixed class that represents fixed-point numbers with these four required members (OCF):

  1. Default constructor - Creates a Fixed with value 0
  2. Copy constructor - Creates a Fixed from another Fixed
  3. Copy assignment operator - Assigns one Fixed to another
  4. Destructor - Cleans up (prints message for this exercise)

Plus basic getters/setters for the raw bits value.

Step 1 - Understand what OCF means

These four functions control how objects are:

  • Created from nothing (default constructor)
  • Created from another object (copy constructor)
  • Assigned from another object (assignment operator)
  • Destroyed (destructor)

Step 2 - Understand fixed-point representation

A fixed-point number stores fractional values using integers:

  • 8 bits for fractional part (right of decimal)
  • Remaining bits for integer part (left of decimal)
  • Value = _rawValue / 256 (since 2^8 = 256)

Step 3 - Print messages to show when each is called

The subject requires printing messages so you can see the order of calls.

Stage 1 - Class declaration:

Fixed.hpp
#ifndef FIXED_HPP
#define FIXED_HPP
class Fixed {
private:
int _rawValue;
static const int _fractionalBits = 8;
public:
Fixed(); // Default constructor
Fixed(const Fixed& other); // Copy constructor
Fixed& operator=(const Fixed& other); // Copy assignment
~Fixed(); // Destructor
int getRawBits() const;
void setRawBits(int const raw);
};
#endif

Stage 2 - Implementation:

Fixed.cpp
#include "Fixed.hpp"
#include <iostream>
Fixed::Fixed() : _rawValue(0) {
std::cout << "Default constructor called" << std::endl;
}
Fixed::Fixed(const Fixed& other) : _rawValue(other._rawValue) {
std::cout << "Copy constructor called" << std::endl;
}
Fixed& Fixed::operator=(const Fixed& other) {
std::cout << "Copy assignment operator called" << std::endl;
if (this != &other)
_rawValue = other._rawValue;
return *this;
}
Fixed::~Fixed() {
std::cout << "Destructor called" << std::endl;
}
int Fixed::getRawBits() const {
std::cout << "getRawBits member function called" << std::endl;
return _rawValue;
}
void Fixed::setRawBits(int const raw) {
_rawValue = raw;
}
LineCodeWhy
Fixed(const Fixed& other)Copy constructor signatureTakes const reference to source
: _rawValue(other._rawValue)Initializer listCopy the value directly
Fixed& operator=(...)Assignment returns referenceEnables chaining: a = b = c
if (this != &other)Self-assignment checkPrevents a = a from breaking
return *thisReturn this objectEnables chaining
static const intClass constantShared by all instances

1. Confusing copy constructor vs assignment

Fixed a;
Fixed b(a); // Copy constructor - b is being CREATED
Fixed c = a; // ALSO copy constructor - c is being CREATED (initialization syntax)
Fixed d;
d = a; // Assignment operator - d already EXISTS

2. Forgetting self-assignment check

// WRONG - breaks if a = a
Fixed& operator=(const Fixed& other) {
_rawValue = other._rawValue;
return *this;
}
// RIGHT - safe for self-assignment
Fixed& operator=(const Fixed& other) {
if (this != &other)
_rawValue = other._rawValue;
return *this;
}

3. Not returning *this from assignment

// WRONG - can't chain assignments
void operator=(const Fixed& other) {
_rawValue = other._rawValue;
}
// RIGHT - enables a = b = c
Fixed& operator=(const Fixed& other) {
// ...
return *this;
}
Terminal window
# Compile and run
c++ -Wall -Wextra -Werror *.cpp -o fixed
# Expected output for basic test:
# Default constructor called
# Copy constructor called
# Copy assignment operator called
# (followed by destructor calls in reverse order)
Fixed.hpp
#ifndef FIXED_HPP
#define FIXED_HPP
class Fixed {
private:
int _rawValue;
static const int _fractionalBits = 8;
public:
Fixed();
Fixed(const Fixed& other);
Fixed& operator=(const Fixed& other);
~Fixed();
int getRawBits() const;
void setRawBits(int const raw);
};
#endif

Exercise 01: Toward a More Useful Fixed-Point

Section titled “Exercise 01: Toward a More Useful Fixed-Point”

Extend the Fixed class with:

  • Constructor from int: Convert integer to fixed-point
  • Constructor from float: Convert float to fixed-point
  • toInt(): Convert fixed-point back to integer
  • toFloat(): Convert fixed-point back to float
  • operator<<: Stream insertion for printing

Step 1 - Understand the math

With 8 fractional bits:

  • int → fixed: Shift left by 8 (multiply by 256)
  • fixed → int: Shift right by 8 (divide by 256)
  • float → fixed: Multiply by 256, then round
  • fixed → float: Divide by 256.0

Step 2 - Why these conversions?

Example: 42.42 in fixed-point
42.42 * 256 = 10859.52 → round to 10860
10860 / 256 = 42.421875 (close to original)

Step 3 - Stream operator is NOT a member

std::cout << fixed means ostream is on the left. We can’t modify ostream, so operator<< must be a non-member function.

Stage 1 - Conversion constructors:

Fixed.cpp
// From int: shift left by fractional bits
Fixed::Fixed(const int value)
: _rawValue(value << _fractionalBits) {
std::cout << "Int constructor called" << std::endl;
}
// From float: multiply by 2^8 and round
Fixed::Fixed(const float value)
: _rawValue(roundf(value * (1 << _fractionalBits))) {
std::cout << "Float constructor called" << std::endl;
}

Stage 2 - Conversion functions:

Fixed.cpp
// To int: shift right (loses fractional part)
int Fixed::toInt() const {
return _rawValue >> _fractionalBits;
}
// To float: divide by 2^8
float Fixed::toFloat() const {
return (float)_rawValue / (1 << _fractionalBits);
}

Stage 3 - Stream operator:

Fixed.cpp
// Non-member function
std::ostream& operator<<(std::ostream& os, const Fixed& fixed) {
os << fixed.toFloat();
return os;
}
LineCodeWhy
value << _fractionalBitsShift left 8 bitsEquivalent to value * 256
roundf(value * ...)Round to nearest intFloat conversion needs rounding
1 << _fractionalBitsEquals 2562^8 = 256
_rawValue >> _fractionalBitsShift right 8 bitsEquivalent to integer division by 256
(float)_rawValue / ...Cast to float firstAvoid integer division!
return osReturn the streamEnables chaining: cout << a << b

1. Forgetting roundf() for float conversion

// WRONG - truncates, loses precision
Fixed::Fixed(const float value)
: _rawValue(value * (1 << _fractionalBits)) {}
// RIGHT - rounds to nearest
Fixed::Fixed(const float value)
: _rawValue(roundf(value * (1 << _fractionalBits))) {}

2. Integer division in toFloat()

// WRONG - integer division gives 0 for small values!
float Fixed::toFloat() const {
return _rawValue / (1 << _fractionalBits);
}
// RIGHT - cast to float first
float Fixed::toFloat() const {
return (float)_rawValue / (1 << _fractionalBits);
}

3. Making operator<< a member function

// WRONG - can't be member (ostream on left side)
class Fixed {
std::ostream& operator<<(std::ostream& os) const;
};
// RIGHT - non-member function
std::ostream& operator<<(std::ostream& os, const Fixed& fixed);
int main() {
Fixed a;
Fixed const b(10); // From int
Fixed const c(42.42f); // From float
Fixed const d(b); // Copy
std::cout << "a is " << a << std::endl; // 0
std::cout << "b is " << b << std::endl; // 10
std::cout << "c is " << c << std::endl; // 42.4219 (close to 42.42)
std::cout << "d is " << d << std::endl; // 10
std::cout << "b as int: " << b.toInt() << std::endl; // 10
std::cout << "c as int: " << c.toInt() << std::endl; // 42
return 0;
}
Fixed.hpp
#ifndef FIXED_HPP
#define FIXED_HPP
#include <iostream>
class Fixed {
private:
int _rawValue;
static const int _fractionalBits = 8;
public:
Fixed();
Fixed(const int value);
Fixed(const float value);
Fixed(const Fixed& other);
Fixed& operator=(const Fixed& other);
~Fixed();
int getRawBits() const;
void setRawBits(int const raw);
float toFloat() const;
int toInt() const;
};
std::ostream& operator<<(std::ostream& os, const Fixed& fixed);
#endif

Add operator overloading to the Fixed class:

  • 6 comparison operators: >, <, >=, <=, ==, !=
  • 4 arithmetic operators: +, -, *, /
  • 4 increment/decrement: pre/post ++, pre/post --
  • 4 static min/max functions: for Fixed references and const references

Step 1 - Comparison operators

Compare raw bits directly - that’s the beauty of fixed-point!

Step 2 - Arithmetic operators

Return a new Fixed object (not modify existing).

Step 3 - Increment: Pre vs Post

  • Pre-increment ++a: Modify and return reference
  • Post-increment a++: Save copy, modify, return old copy

Step 4 - The smallest increment

Adding 1 to raw bits = adding 1/256 = 0.00390625 to the value.

Stage 1 - Comparison operators:

Fixed.cpp
bool Fixed::operator>(const Fixed& other) const {
return _rawValue > other._rawValue;
}
bool Fixed::operator<(const Fixed& other) const {
return _rawValue < other._rawValue;
}
bool Fixed::operator>=(const Fixed& other) const {
return _rawValue >= other._rawValue;
}
bool Fixed::operator<=(const Fixed& other) const {
return _rawValue <= other._rawValue;
}
bool Fixed::operator==(const Fixed& other) const {
return _rawValue == other._rawValue;
}
bool Fixed::operator!=(const Fixed& other) const {
return _rawValue != other._rawValue;
}

Stage 2 - Arithmetic operators:

Fixed.cpp
Fixed Fixed::operator+(const Fixed& other) const {
return Fixed(toFloat() + other.toFloat());
}
Fixed Fixed::operator-(const Fixed& other) const {
return Fixed(toFloat() - other.toFloat());
}
Fixed Fixed::operator*(const Fixed& other) const {
return Fixed(toFloat() * other.toFloat());
}
Fixed Fixed::operator/(const Fixed& other) const {
return Fixed(toFloat() / other.toFloat());
}

Stage 3 - Increment/decrement:

Fixed.cpp
// Pre-increment: ++a
Fixed& Fixed::operator++() {
_rawValue++;
return *this;
}
// Post-increment: a++
Fixed Fixed::operator++(int) {
Fixed temp(*this); // Save current value
_rawValue++; // Increment
return temp; // Return OLD value
}
// Pre-decrement: --a
Fixed& Fixed::operator--() {
_rawValue--;
return *this;
}
// Post-decrement: a--
Fixed Fixed::operator--(int) {
Fixed temp(*this);
_rawValue--;
return temp;
}

Stage 4 - Static min/max:

Fixed.cpp
Fixed& Fixed::min(Fixed& a, Fixed& b) {
return (a < b) ? a : b;
}
const Fixed& Fixed::min(const Fixed& a, const Fixed& b) {
return (a < b) ? a : b;
}
Fixed& Fixed::max(Fixed& a, Fixed& b) {
return (a > b) ? a : b;
}
const Fixed& Fixed::max(const Fixed& a, const Fixed& b) {
return (a > b) ? a : b;
}
LineCodeWhy
operator++(int)Post-increment signatureint parameter distinguishes from pre
Fixed temp(*this)Save current valuePost-increment returns old value
return *thisReturn referencePre-increment returns modified object
return tempReturn copyPost-increment returns saved copy
static Fixed& min(...)Static functionCalled as Fixed::min(a, b)
Two min overloadsFor const/non-constPreserves const-correctness

1. Wrong return types for pre/post increment

// WRONG - post should return copy, not reference
Fixed& operator++(int) {
// ...
}
// RIGHT
Fixed& operator++(); // Pre returns reference
Fixed operator++(int); // Post returns copy

2. Not understanding the smallest increment

Fixed a(0);
a++; // a is now 0.00390625 (1/256)
// NOT 1.0!

3. Forgetting both const and non-const versions of min/max

// Need both:
static Fixed& min(Fixed& a, Fixed& b); // For non-const
static const Fixed& min(const Fixed& a, const Fixed& b); // For const
int main() {
Fixed a;
Fixed const b(Fixed(5.05f) * Fixed(2));
std::cout << a << std::endl; // 0
std::cout << ++a << std::endl; // 0.00390625
std::cout << a << std::endl; // 0.00390625
std::cout << a++ << std::endl; // 0.00390625
std::cout << a << std::endl; // 0.0078125
std::cout << b << std::endl; // 10.1016
std::cout << Fixed::max(a, b) << std::endl; // 10.1016
return 0;
}
Fixed.hpp (operators section)
// Comparison
bool operator>(const Fixed& other) const;
bool operator<(const Fixed& other) const;
bool operator>=(const Fixed& other) const;
bool operator<=(const Fixed& other) const;
bool operator==(const Fixed& other) const;
bool operator!=(const Fixed& other) const;
// Arithmetic
Fixed operator+(const Fixed& other) const;
Fixed operator-(const Fixed& other) const;
Fixed operator*(const Fixed& other) const;
Fixed operator/(const Fixed& other) const;
// Increment/Decrement
Fixed& operator++();
Fixed operator++(int);
Fixed& operator--();
Fixed operator--(int);
// Static min/max
static Fixed& min(Fixed& a, Fixed& b);
static const Fixed& min(const Fixed& a, const Fixed& b);
static Fixed& max(Fixed& a, Fixed& b);
static const Fixed& max(const Fixed& a, const Fixed& b);

Exercise 03: BSP (Binary Space Partitioning)

Section titled “Exercise 03: BSP (Binary Space Partitioning)”

Implement a function to test if a point is inside a triangle:

bool bsp(Point const a, Point const b, Point const c, Point const point);
  • Returns true if point is strictly inside the triangle
  • Returns false if point is on an edge or vertex

You must also create a Point class with const x and y attributes.

Step 1 - Understand the algorithm (Cross Product Method)

For each edge of the triangle:

  1. Compute the cross product of (edge vector) x (point-to-vertex vector)
  2. If all three cross products have the same sign, point is inside
  3. If any cross product is zero, point is on an edge

Step 2 - Design the Point class

Key constraint: x and y are const - can’t be changed after construction.

Step 3 - The cross product formula

For vectors from P1 to P2 and P1 to P3:

cross = (P2.x - P1.x) * (P3.y - P1.y) - (P2.y - P1.y) * (P3.x - P1.x)

Stage 1 - Point class:

Point.hpp
#ifndef POINT_HPP
#define POINT_HPP
#include "Fixed.hpp"
class Point {
private:
Fixed const _x;
Fixed const _y;
public:
Point();
Point(const float x, const float y);
Point(const Point& other);
Point& operator=(const Point& other);
~Point();
Fixed getX() const;
Fixed getY() const;
};
bool bsp(Point const a, Point const b, Point const c, Point const point);
#endif

Stage 2 - Point implementation:

Point.cpp
#include "Point.hpp"
Point::Point() : _x(0), _y(0) {}
Point::Point(const float x, const float y) : _x(x), _y(y) {}
Point::Point(const Point& other) : _x(other._x), _y(other._y) {}
// Assignment can't modify const members - effectively does nothing
Point& Point::operator=(const Point& other) {
(void)other;
return *this;
}
Point::~Point() {}
Fixed Point::getX() const { return _x; }
Fixed Point::getY() const { return _y; }

Stage 3 - BSP algorithm:

bsp.cpp
#include "Point.hpp"
// Cross product tells us which side of a line a point is on
static Fixed crossProduct(Point const& p1, Point const& p2, Point const& p3) {
return (p2.getX() - p1.getX()) * (p3.getY() - p1.getY())
- (p2.getY() - p1.getY()) * (p3.getX() - p1.getX());
}
bool bsp(Point const a, Point const b, Point const c, Point const point) {
// Compute cross products for each edge
Fixed d1 = crossProduct(a, b, point);
Fixed d2 = crossProduct(b, c, point);
Fixed d3 = crossProduct(c, a, point);
// Check if on any edge (cross product = 0)
if (d1 == Fixed(0) || d2 == Fixed(0) || d3 == Fixed(0))
return false;
// Check if all same sign (all positive OR all negative)
bool allNegative = (d1 < Fixed(0)) && (d2 < Fixed(0)) && (d3 < Fixed(0));
bool allPositive = (d1 > Fixed(0)) && (d2 > Fixed(0)) && (d3 > Fixed(0));
return allNegative || allPositive;
}
LineCodeWhy
Fixed const _xConst memberX coordinate can’t change
(void)otherSuppress warningAssignment can’t do anything useful
crossProduct(a, b, point)Edge AB to pointSign tells us which side
d1 == Fixed(0)On edge checkZero cross = collinear = on edge
allNegative || allPositiveInside checkSame sign means same side of all edges

1. Returning true for points on edges

// WRONG - subject says edge/vertex = false
if (d1 == Fixed(0))
return true;
// RIGHT
if (d1 == Fixed(0))
return false;

2. Trying to modify const members in assignment

// WRONG - won't compile, _x and _y are const
Point& Point::operator=(const Point& other) {
_x = other._x; // Error!
_y = other._y; // Error!
return *this;
}
// RIGHT - acknowledge it does nothing useful
Point& Point::operator=(const Point& other) {
(void)other;
return *this;
}

3. Wrong cross product formula

// WRONG - wrong order of operations
(p2.getX() - p1.getX()) * (p2.getY() - p1.getY())
// RIGHT
(p2.getX() - p1.getX()) * (p3.getY() - p1.getY())
- (p2.getY() - p1.getY()) * (p3.getX() - p1.getX())
int main() {
// Triangle with vertices at (0,0), (10,0), (5,10)
Point a(0.0f, 0.0f);
Point b(10.0f, 0.0f);
Point c(5.0f, 10.0f);
// Test cases
Point inside(5.0f, 3.0f); // Inside
Point onEdge(5.0f, 0.0f); // On edge AB
Point onVertex(0.0f, 0.0f); // On vertex A
Point outside(15.0f, 5.0f); // Outside
std::cout << "Inside: " << bsp(a, b, c, inside) << std::endl; // 1
std::cout << "On edge: " << bsp(a, b, c, onEdge) << std::endl; // 0
std::cout << "On vertex: " << bsp(a, b, c, onVertex) << std::endl; // 0
std::cout << "Outside: " << bsp(a, b, c, outside) << std::endl; // 0
return 0;
}
bsp.cpp
#include "Point.hpp"
static Fixed crossProduct(Point const& p1, Point const& p2, Point const& p3) {
return (p2.getX() - p1.getX()) * (p3.getY() - p1.getY())
- (p2.getY() - p1.getY()) * (p3.getX() - p1.getX());
}
bool bsp(Point const a, Point const b, Point const c, Point const point) {
Fixed d1 = crossProduct(a, b, point);
Fixed d2 = crossProduct(b, c, point);
Fixed d3 = crossProduct(c, a, point);
if (d1 == Fixed(0) || d2 == Fixed(0) || d3 == Fixed(0))
return false;
bool allNegative = (d1 < Fixed(0)) && (d2 < Fixed(0)) && (d3 < Fixed(0));
bool allPositive = (d1 > Fixed(0)) && (d2 > Fixed(0)) && (d3 > Fixed(0));
return allNegative || allPositive;
}