This project has been created as part of the 42 curriculum by baelgadi.
Ad-hoc polymorphism, operator overloading and the Orthodox Canonical class form
No powers. No shortcuts.
Every criminal in Gotham knows what happens when they face someone with a plan.
A class with no defined copy, no defined assignment and no defined destructor is merely a criminal waiting to happen.
C++ gives us operators and CPP02 teaches us to own them.
|
CPP Module 02:
4 exercises. C++98 standard only. Compiler: c++ · Flags: -Wall -Wextra -Werror · Must compile with -std=c++98Exercise 00: My First Class in Orthodox Canonical Form · ex00/ · OCF + raw accessorsExercise 01: Towards a more useful fixed-point number class · ex01/ · Conversions + operator<<Exercise 02: Now we’re talking · ex02/ · Full operator suiteExercise 03: BSP · ex03/ · Point-in-triangle with Fixed arithmeticForbidden: using namespace (-42) · friend (-42) · printf · *alloc · free · STL containers · STL algorithmsAllowed: roundf from <cmath> (ex01–03) |
I have a contingency plan for every scenario. The OCF is yours.
From module 02 onwards, every class you write must define these 4 members.
| OCF Member | What it does |
|---|---|
| Default constructor | Creates an object with a known state |
| Copy constructor | Creates a new object from an existing one |
| Copy assignment operator | Overwrites an existing object |
| Destructor | Cleans up when the object is destroyed |
Without them, the compiler auto-generates shallow.
Safe here but lethal later, when a class owns heap memory.
class Fixed {
public:
Fixed(void);
Fixed(Fixed const &other);
Fixed &operator=(Fixed const &other);
~Fixed(void);
};[!WARNING] The copy constructor is called on
Fixed b(a).
The copy assignment operator is called onb = a, only whenbalready exists.
They are not the same.
Floating-point is a guess while fixed-point is a contract.
We store numbers as integers scaled by 256 (2^8). (8 fractional bits)
raw value 256 → 256 / 256 = 1.0
raw value 384 → 384 / 256 = 1.5
raw value 1 → 1 / 256 = 0.00390625 (the epsilon — the smallest representable step)
Conversions:
// int → fixed-point
_value = n << 8; // multiply by 256
// float → fixed-point
_value = roundf(f * 256.0f); // multiply and round (not truncate)
// fixed-point → float
return (float)_value / 256.0f;
// fixed-point → int
return _value >> 8; // divide by 256, truncates toward zeroArithmetic:
// Addition / subtraction: same scale but just operate on raw values
result._value = _value + other._value;
// Multiplication: (a * 256) * (b * 256) = (a * b) * 256^2 —(shift back by 8)
result._value = ((long long)_value * other._value) >> 8;
// Division: (a * 256) / (b * 256) = a / b (scale up first or lose the fraction)
result._value = ((long long)_value << 8) / other._value;[!WARNING] Cast to
long longbefore multiplying.
2intvalues near2^24produce a2^48product that silently overflows 32 bits.
In C, a + b only works for built-in types. You’d call add(a, b) for anything else.
In C++ however, we define what + means for our type.
// This:
Fixed c = a + b;
// is this:
Fixed c = a.operator+(b);Member vs non member:
| Member | Non member | |
|---|---|---|
| Left operand | this |
First parameter |
| Use when | Operator belongs to your type | Left operand isn’t your type |
| Example | bool operator>(const Fixed &rhs) const |
std::ostream &operator<<(...) |
operator<< must be non member: the left operand is std::ostream, which we don’t own.
std::ostream &operator<<(std::ostream &out, Fixed const &f)
{
out << f.toFloat(); // uses public toFloat(), not _value directly
return out;
}[!NOTE] Using
friendto giveoperator<<access to_valueis not allowed.
Both operators share the same symbol. The (int) dummy parameter is how C++ tells them apart.
// Pre increment: modify and return the modified object
Fixed &Fixed::operator++(void)
{
++_value;
return *this; // reference — no copy made
}
// Post increment: save, modify and return the old state
Fixed Fixed::operator++(int)
{
Fixed tmp(*this); // snapshot before the change
++_value;
return tmp; // by value — tmp is local, never return its reference
}Pre (++a) |
Post (a++) |
|
|---|---|---|
| Returns | Reference to *this |
Copy of old state |
| Return type | Fixed& |
Fixed |
| Signature | operator++(void) |
operator++(int) |
The increment unit is 1 raw bit = 1/256 ≈ 0.00390625 (the smallest step the system can represent).
Given triangle ABC and a point P. Is P strictly inside?
Cross products tell us which side of an edge a point is on.
cross(AB, AP) = (B.x - A.x) * (P.y - A.y) - (B.y - A.y) * (P.x - A.x)
Positive → P is to the LEFT of edge A→B
Zero → P is exactly ON the line through A and B
Negative → P is to the RIGHT
Compute for all three edges. If all three are the same sign and none is zero — P is inside.
bool bsp(Point const a, Point const b, Point const c, Point const point)
{
Fixed d1 = cross(a, b, point);
Fixed d2 = cross(b, c, point);
Fixed d3 = cross(c, a, point);
if (d1 == Fixed(0) || d2 == Fixed(0) || d3 == Fixed(0))
return false; // on edge or vertex (subject requires false)
bool allPos = (d1 > Fixed(0)) && (d2 > Fixed(0)) && (d3 > Fixed(0));
bool allNeg = (d1 < Fixed(0)) && (d2 < Fixed(0)) && (d3 < Fixed(0));
return allPos || allNeg;
}[!NOTE]
PointhasFixed const _x, _y. The copy assignment operator must exist for OCF, but its body does nothing.
You cannot reassign const members.Point &Point::operator=(const Point &other) { (void)other; return *this; }
operator<< printing _value instead of toFloat()
_value = 256 represents 1.0. The output must show 1, not 256.
Missing const overloads for min/max
The subject requires 4 overloads: 2 non-const + 2 const.
Without the const versions, Fixed::min with const arguments fails to compile.
Post increment returning a reference
// WRONG (dangling reference to a local)
Fixed &Fixed::operator++(int) { Fixed tmp(*this); ++_value; return tmp; }
// RIGHT (return by value)
Fixed Fixed::operator++(int) { Fixed tmp(*this); ++_value; return tmp; }Missing long long in multiplication
int * int overflows at 2^31. Cast before multiplying, not after.
result._value = ((long long)_value * other._value) >> 8;BSP returning true on edges
Zero cross product = point on edge = return false.
Point assignment casting away const
// WRONG (UB)
*(Fixed *)&_x = other._x;
// RIGHT (body does nothing)
Point &Point::operator=(const Point &other) { (void)other; return *this; }# ──── ex00 ────
cd ex00 && make
./fixed
# ──── ex01 ────
cd ex01 && make
./fixed
# ──── ex02 ────
cd ex02 && make
./fixed | grep -v called
# ──── ex03 ────
cd ex03 && make
./bspEvaluator demo for ex02:
Fixed a(5), b(3);
std::cout << (a > b) << " " << (a < b) << " " << (a == b) << std::endl; // 1 0 0
std::cout << a + b << " " << a - b << " " << a * b << " " << a / b << std::endl; // 8 2 15 1.66406
const Fixed x(1), y(2);
std::cout << Fixed::min(x, y) << std::endl; // 1 const overload
std::cout << Fixed::max(x, y) << std::endl; // 2 const overloadDo you bleed? Override it.
You either die a fixed-point, or you live long enough to see yourself become a float.