6.06 Class Invariant Enforcement & Preventing the Static Initialization Order Fiasco
💻 1. Annotated Source Code
#include <iostream> #include "Triangle.h" using namespace std; void printTriangleData(const Triangle& t); int main() { Triangle t1; Triangle t2(3, 4, 5); Triangle t3(1, 2, 3); printTriangleData(t1); printTriangleData(t2); printTriangleData(t3); return 0; } void printTriangleData(const Triangle& t) { cout << "Sides:" << t.getSideA() << ", " << t.getSideB() << ", " << t.getSideC() << endl; if (t.isValid()) { cout << "\tPerimeter: " << t.perimeter() << endl; cout << "\tArea: " << t.perimeter() << endl; } else { cout << "\tThis is not a valid triangle" << endl; } cout << endl; }
#ifndef TRIANGLE_H #define TRIANGLE_H class Triangle { public: Triangle(); Triangle(double sideA, double sideB, double sideC); double getSideA() const; double getSideB() const; double getSideC() const; void setSideA(double sideA); void setSideB(double sideB); void setSideC(double sideC); bool isValid() const; double perimeter() const; double area() const; private: double sideA; double sideB; double sideC; }; #endif
#include "Triangle.h" #include <cmath> //sqrt #include <iostream> using namespace std; Triangle::Triangle() { sideA = 1; sideB = 1; sideC = 1; } Triangle::Triangle(double sideA, double sideB, double sideC) { this->sideA = sideA; this->sideB = sideB; this->sideC = sideC; } double Triangle::getSideA() const { return sideA; } double Triangle::getSideB() const { return sideB; } double Triangle::getSideC() const { return sideC; } void Triangle::setSideA(double sideA) { this->sideA = sideA; } void Triangle::setSideB(double sideB) { this->sideB = sideB; } void Triangle::setSideC(double sideC) { this->sideC = sideC; } bool Triangle::isValid() const { return (sideA + sideB > sideC) && (sideA + sideC > sideB) && (sideB + sideC > sideA); } double Triangle::perimeter() const { return sideA + sideB + sideC; } double Triangle::area() const { if (!isValid()) { cout << "Cannot compute area: triangle is invalid." << endl; return 0; } double s = perimeter() / 2.0; return sqrt(s * (s - sideA) * (s - sideB) * (s - sideC)); }
📐 2. Architecture & UML Class Model
📚 3. Core C++ Concepts Deep-Dive
1. Multi-Field Invariant Enforcement
A valid triangle must satisfy the Triangle Inequality Theorem: the sum of the lengths of any two sides must be strictly greater than the length of the remaining side ($a + b > c$, $a + c > b$, and $b + c > a$).
⚡ 4. Embedded Systems & Hardware Reality
1. The Static Initialization Order Fiasco
When multiple global C++ objects exist across different .cpp files, the order in which their constructors execute before main() is undefined by the C++ standard.
If global object A (e.g. DisplayDriver) accesses global object B (e.g. SpiBusDriver) inside its constructor, and B has not yet initialized its hardware registers, the microcontroller will crash with a fatal HardFault before reaching main()!
2. The Construct-On-First-Use Idiom (Meyers Singleton)
Wrapping static instances inside a function returning a reference guarantees the object is constructed upon its first call, completely eliminating initialization order bugs.
💡 5. Production-Ready Embedded Refactoring
Construct-On-First-Use idiom preventing bootloader crashes:
#include <cstdint> class SpiBusManager { private: SpiBusManager() noexcept { // Initialize SPI hardware registers safely... } public: // Guaranteed to initialize safely on first call! static SpiBusManager& instance() noexcept { static SpiBusManager bus; // Meyers' Singleton return bus; } void write(uint8_t byte) noexcept { /* ... */ } };
📝 Knowledge Verification Quiz
Test your understanding of the C++ concepts and embedded microcontroller trade-offs covered in this guide. Click any option for instant feedback.
.data and zeroing .bss) calls __libc_init_array to invoke all global C++ constructors before branching to main().