System Programming Topic

C++ System-Level & Professional Topics

1. Compilation Process

C++ compilation is a multi-stage process that converts source code into executable programs.

The Four Stages:

cpp

// Example: hello.cpp
#include <iostream>
using namespace std;

int main() {
    cout << "Hello, World!" << endl;
    return 0;
}

Stage 1: Preprocessing

The preprocessor processes directives starting with #.

bash

# View preprocessed output
g++ -E hello.cpp -o hello.ii
# or
cpp hello.cpp > hello.ii

What happens:

  1. Includes: #include inserts header files

  2. Macros: #define expansions

  3. Conditional Compilation: #ifdef, #if, #endif

  4. Removes comments

Example:

cpp

// Original: hello.cpp
#include <iostream>
#define MESSAGE "Hello"
using namespace std;

int main() {
#ifdef DEBUG
    cout << "Debug mode" << endl;
#endif
    cout << MESSAGE << endl;
    return 0;
}

// After preprocessing (partial):
namespace std {
    // ... iostream contents ...
}
using namespace std;

int main() {
    // #ifdef DEBUG was false, so removed
    cout << "Hello" << endl;  // MESSAGE replaced
    return 0;
}

Stage 2: Compilation

Converts preprocessed code to assembly language.

bash

# Compile to assembly
g++ -S hello.ii -o hello.s
# or directly from source
g++ -S hello.cpp -o hello.s

Assembly output (x86-64):

assembly

    .section    __TEXT,__text,regular,pure_instructions
    .build_version macos, 11, 0
    .globl  _main
    .p2align    4, 0x90
_main:
    pushq   %rbp
    movq    %rsp, %rbp
    subq    $16, %rsp
    movl    $0, -4(%rbp)
    leaq    L_.str(%rip), %rdi
    movb    $0, %al
    callq   _printf
    xorl    %eax, %eax
    addq    $16, %rsp
    popq    %rbp
    retq
    
    .section    __TEXT,__cstring,cstring_literals
L_.str:
    .asciz  "Hello, World!\n"

Stage 3: Assembly

Converts assembly to machine code (object files).

bash

# Assemble to object file
g++ -c hello.s -o hello.o
# or directly
g++ -c hello.cpp -o hello.o

Object file contains:

  • Machine code

  • Symbol table (function/variable names)

  • Relocation information

  • Debug information (if compiled with -g)

Stage 4: Linking

Combines object files and libraries into executable.

bash

# Link object file
g++ hello.o -o hello
# Link multiple files
g++ main.o utils.o -o program

What happens during linking:

  1. Symbol Resolution: Matches references with definitions

  2. Relocation: Adjusts addresses

  3. Library Linking: Links static/dynamic libraries

  4. Final Executable: Creates binary file

Complete Compilation Example

bash

# Single command (does all steps)
g++ hello.cpp -o hello

# Step-by-step
g++ -E hello.cpp -o hello.i      # Preprocess
g++ -S hello.i -o hello.s        # Compile
as hello.s -o hello.o            # Assemble
g++ hello.o -o hello             # Link

# With optimization and debugging
g++ -O2 -g -Wall hello.cpp -o hello

Common Compiler Flags

bash

# Optimization levels
-O0    # No optimization (default, good for debugging)
-O1    # Basic optimization
-O2    # More optimization (recommended for release)
-O3    # Aggressive optimization
-Os    # Optimize for size

# Debugging
-g              # Include debug symbols
-ggdb           # Debug info for GDB
-DDEBUG         # Define macro DEBUG

# Warnings
-Wall           # All common warnings
-Wextra         # Extra warnings
-Werror         # Treat warnings as errors
-Wpedantic      # Strict ISO C++ compliance

# Language standards
-std=c++11      # C++11 standard
-std=c++14      # C++14 standard
-std=c++17      # C++17 standard
-std=c++20      # C++20 standard

# Architecture
-march=native   # Optimize for current CPU
-mtune=native   # Tune for current CPU

# Others
-fPIC           # Position Independent Code (for shared libraries)
-shared         # Create shared library
-static         # Static linking
-pthread        # POSIX threads support

Header Files vs Source Files

cpp

// math_utils.h (HEADER - declarations)
#ifndef MATH_UTILS_H  // Include guard
#define MATH_UTILS_H

double add(double a, double b);
double multiply(double a, double b);

#endif

// math_utils.cpp (SOURCE - definitions)
#include "math_utils.h"

double add(double a, double b) {
    return a + b;
}

double multiply(double a, double b) {
    return a * b;
}

// main.cpp
#include "math_utils.h"
#include <iostream>

int main() {
    std::cout << add(5, 3) << std::endl;
    std::cout << multiply(5, 3) << std::endl;
    return 0;
}

bash

# Compile multiple files
g++ -c math_utils.cpp -o math_utils.o
g++ -c main.cpp -o main.o
g++ main.o math_utils.o -o program

2. Build Systems

Makefile Basics

Make automates building from source code.

Simple Makefile:

makefile

# Comments start with #
# Variables
CC = g++
CFLAGS = -Wall -std=c++11
TARGET = program
OBJS = main.o math_utils.o

# Default target (first one)
all: $(TARGET)

# Link object files to create executable
$(TARGET): $(OBJS)
	$(CC) $(CFLAGS) -o $(TARGET) $(OBJS)

# Compile main.cpp to main.o
main.o: main.cpp math_utils.h
	$(CC) $(CFLAGS) -c main.cpp

# Compile math_utils.cpp to math_utils.o
math_utils.o: math_utils.cpp math_utils.h
	$(CC) $(CFLAGS) -c math_utils.cpp

# Clean up build files
clean:
	rm -f $(OBJS) $(TARGET)

# Phony targets (not actual files)
.PHONY: all clean

Advanced Makefile:

makefile

# More professional Makefile
CXX = g++
CXXFLAGS = -Wall -Wextra -std=c++17 -O2
LDFLAGS = -lm
TARGET = app
BUILD_DIR = build
SRC_DIR = src
INC_DIR = include

# Find all source files
SRCS = $(wildcard $(SRC_DIR)/*.cpp)
# Convert source names to object names in build directory
OBJS = $(patsubst $(SRC_DIR)/%.cpp, $(BUILD_DIR)/%.o, $(SRCS))
# Auto dependency files
DEPS = $(OBJS:.o=.d)

# Include dependency files
-include $(DEPS)

# Default target
all: $(BUILD_DIR)/$(TARGET)

# Link executable
$(BUILD_DIR)/$(TARGET): $(OBJS)
	@mkdir -p $(BUILD_DIR)
	$(CXX) $(CXXFLAGS) -o $@ $^ $(LDFLAGS)
	@echo "Build complete: $@"

# Compile source files with dependency generation
$(BUILD_DIR)/%.o: $(SRC_DIR)/%.cpp
	@mkdir -p $(BUILD_DIR)
	$(CXX) $(CXXFLAGS) -I$(INC_DIR) -MMD -c $< -o $@

# Clean
clean:
	rm -rf $(BUILD_DIR)

# Run
run: all
	./$(BUILD_DIR)/$(TARGET)

# Debug build
debug: CXXFLAGS += -g -O0 -DDEBUG
debug: clean all

# Release build
release: CXXFLAGS += -O3 -DNDEBUG
release: clean all

.PHONY: all clean run debug release

Using the Makefile:

bash

make              # Build default target
make all          # Same as above
make clean        # Clean build files
make run          # Build and run
make debug        # Build with debug flags
make release      # Build with optimization

# Parallel build (use multiple cores)
make -j4

# Show commands without executing
make -n

# Debug make
make --debug

CMake Introduction

CMake is a cross-platform build system generator.

Minimal CMakeLists.txt:

cmake

# CMakeLists.txt
cmake_minimum_required(VERSION 3.10)
project(MyProject VERSION 1.0.0)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

add_executable(myapp main.cpp math_utils.cpp)

Basic Usage:

bash

# Create build directory
mkdir build
cd build

# Generate build system
cmake ..

# Build
cmake --build .

# Or use generated Makefile
make

Complete CMakeLists.txt Example:

cmake

cmake_minimum_required(VERSION 3.15)
project(AdvancedProject 
    VERSION 1.0.0
    DESCRIPTION "Advanced C++ Project"
    LANGUAGES CXX
)

# Options
option(BUILD_TESTS "Build tests" ON)
option(BUILD_EXAMPLES "Build examples" OFF)

# Compiler settings
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)

# Compiler flags
if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Wpedantic")
    set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -g -O0")
    set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O3 -DNDEBUG")
endif()

# Include directories
include_directories(${CMAKE_SOURCE_DIR}/include)

# Add main executable
add_executable(main_app 
    src/main.cpp
    src/math_utils.cpp
    src/logger.cpp
)

# Add library
add_library(math_lib STATIC
    src/math_functions.cpp
)

# Link libraries
target_link_libraries(main_app math_lib)

# Add tests if enabled
if(BUILD_TESTS)
    enable_testing()
    
    # Find testing framework (Google Test example)
    find_package(GTest REQUIRED)
    
    add_executable(run_tests
        tests/test_math.cpp
        tests/test_logger.cpp
    )
    
    target_link_libraries(run_tests 
        math_lib
        GTest::GTest
        GTest::Main
    )
    
    add_test(NAME math_tests COMMAND run_tests)
endif()

# Install targets
install(TARGETS main_app 
    RUNTIME DESTINATION bin
    LIBRARY DESTINATION lib
    ARCHIVE DESTINATION lib
)

install(DIRECTORY include/ DESTINATION include)

Modern CMake (target-based):

cmake

cmake_minimum_required(VERSION 3.15)
project(ModernCppProject)

# Create library target
add_library(MathFunctions STATIC
    src/math_functions.cpp
)

# Set properties for the target
target_include_directories(MathFunctions
    PUBLIC
        $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
        $<INSTALL_INTERFACE:include>
)

target_compile_features(MathFunctions PUBLIC cxx_std_17)

# Create executable target
add_executable(MainApp src/main.cpp)

# Link library to executable
target_link_libraries(MainApp PRIVATE MathFunctions)

# Add compile definitions conditionally
target_compile_definitions(MathFunctions
    PRIVATE
        $<$<CONFIG:Debug>:DEBUG_MODE>
)

# Install targets
install(TARGETS MathFunctions MainApp
    EXPORT MathFunctionsTargets
    LIBRARY DESTINATION lib
    ARCHIVE DESTINATION lib
    RUNTIME DESTINATION bin
)

# Install headers
install(DIRECTORY include/ DESTINATION include)

Using CMake:

bash

# Out-of-source build
mkdir build && cd build

# Configure
cmake .. -DCMAKE_BUILD_TYPE=Debug -DBUILD_TESTS=ON

# Build
cmake --build . --config Debug

# Install
cmake --install . --prefix ../install

# Generate IDE projects
cmake .. -G "Visual Studio 16 2019"  # VS on Windows
cmake .. -G "Xcode"                   # Xcode on macOS
cmake .. -G "Unix Makefiles"          # Makefiles on Unix

# Using ccmake (curses interface)
ccmake ..

# Using cmake-gui
cmake-gui .

3. Performance Optimization

Time & Space Complexity

Understanding Big O notation is crucial for performance.

cpp

// O(1) - Constant time
int getFirst(const vector<int>& vec) {
    return vec.empty() ? 0 : vec[0];
}

// O(n) - Linear time
int findIndex(const vector<int>& vec, int target) {
    for(size_t i = 0; i < vec.size(); i++) {
        if(vec[i] == target) return i;
    }
    return -1;
}

// O(n²) - Quadratic time (BAD for large n)
vector<vector<int>> generatePairs(const vector<int>& vec) {
    vector<vector<int>> result;
    for(size_t i = 0; i < vec.size(); i++) {
        for(size_t j = 0; j < vec.size(); j++) {
            result.push_back({vec[i], vec[j]});
        }
    }
    return result;
}

// O(log n) - Logarithmic time
int binarySearch(const vector<int>& vec, int target) {
    int left = 0, right = vec.size() - 1;
    while(left <= right) {
        int mid = left + (right - left) / 2;
        if(vec[mid] == target) return mid;
        if(vec[mid] < target) left = mid + 1;
        else right = mid - 1;
    }
    return -1;
}

// O(n log n) - Linearithmic time
void mergeSort(vector<int>& vec, int left, int right) {
    if(left < right) {
        int mid = left + (right - left) / 2;
        mergeSort(vec, left, mid);
        mergeSort(vec, mid + 1, right);
        merge(vec, left, mid, right);
    }
}

// Space complexity examples
void O1_Space() {  // O(1) space
    int a = 1, b = 2, c = 3;
    // Fixed number of variables
}

void On_Space(const vector<int>& input) {  // O(n) space
    vector<int> copy = input;  // Creates copy of input
    // Additional O(n) space used
}

void On2_Space(int n) {  // O(n²) space
    vector<vector<int>> matrix(n, vector<int>(n));
    // n × n matrix
}

Compiler Optimizations

Modern compilers can perform sophisticated optimizations.

cpp

// Example: Loop optimization
vector<int> squareElements(const vector<int>& input) {
    vector<int> result;
    result.reserve(input.size());  // Pre-allocate memory
    
    // Compiler can optimize this loop
    for(int x : input) {
        result.push_back(x * x);
    }
    return result;
}

// Common compiler flags for optimization
// g++/clang: -O1, -O2, -O3, -Os, -Ofast
// MSVC: /O1, /O2, /Ox

// Specific optimizations:
// -funroll-loops      # Loop unrolling
// -finline-functions  # Function inlining
// -flto               # Link Time Optimization
// -march=native       # CPU-specific optimizations

// Write optimizer-friendly code:
class Optimized {
private:
    int* data;
    size_t size;
    
public:
    // Use const where possible
    int sum() const {
        int total = 0;
        // Simple loops are easier to optimize
        for(size_t i = 0; i < size; i++) {
            total += data[i];
        }
        return total;
    }
    
    // Avoid aliasing issues
    void scale(int factor) {
        // Use restrict keyword (C99) or compiler-specific
        int* __restrict ptr = data;
        for(size_t i = 0; i < size; i++) {
            ptr[i] *= factor;
        }
    }
};

// Compiler barriers
#include <atomic>
void memoryBarrier() {
    std::atomic_thread_fence(std::memory_order_seq_cst);
}

// Likely/unlikely hints (C++20)
void process(int value) {
    if(value == 0) [[unlikely]] {
        // Rare case
        handleZero();
    } else [[likely]] {
        // Common case
        handleNonZero(value);
    }
}

Profiling Tools

Identifying performance bottlenecks.

gprof (GNU Profiler):

cpp

// Example.cpp
#include <iostream>
#include <vector>
#include <algorithm>
#include <chrono>
using namespace std;

void slowFunction() {
    vector<int> data(1000000);
    for(int& x : data) {
        x = rand() % 1000;
    }
    // Inefficient sort
    for(size_t i = 0; i < data.size(); i++) {
        for(size_t j = i + 1; j < data.size(); j++) {
            if(data[i] > data[j]) {
                swap(data[i], data[j]);
            }
        }
    }
}

void fastFunction() {
    vector<int> data(1000000);
    for(int& x : data) {
        x = rand() % 1000;
    }
    // Efficient sort
    sort(data.begin(), data.end());
}

int main() {
    auto start = chrono::high_resolution_clock::now();
    
    slowFunction();
    fastFunction();
    
    auto end = chrono::high_resolution_clock::now();
    auto duration = chrono::duration_cast<chrono::milliseconds>(end - start);
    cout << "Time: " << duration.count() << "ms" << endl;
    
    return 0;
}

bash

# Compile with profiling
g++ -pg -O2 Example.cpp -o Example

# Run program
./Example

# Analyze with gprof
gprof Example gmon.out > analysis.txt

# View call graph
gprof -q Example gmon.out > callgraph.txt

perf (Linux Performance Counters):

bash

# Record performance data
perf record ./Example

# Analyze
perf report

# Specific analysis
perf stat ./Example          # Basic statistics
perf top                     # Real-time profiling
perf annotate                # Annotate source with performance data

Valgrind (Memory/Cache Profiling):

bash

# Memory profiling
valgrind --tool=memcheck ./Example

# Cache profiling
valgrind --tool=cachegrind ./Example
kcachegrind cachegrind.out.<pid>

# Call graph profiling
valgrind --tool=callgrind ./Example
callgrind_annotate callgrind.out.<pid>

Custom Profiling in Code:

cpp

#include <chrono>
#include <iostream>
#include <map>
#include <string>

class Profiler {
private:
    struct ProfileData {
        long long totalTime = 0;
        int callCount = 0;
        std::chrono::high_resolution_clock::time_point startTime;
    };
    
    static std::map<std::string, ProfileData> data;
    
public:
    static void start(const std::string& name) {
        data[name].startTime = std::chrono::high_resolution_clock::now();
    }
    
    static void stop(const std::string& name) {
        auto& entry = data[name];
        auto endTime = std::chrono::high_resolution_clock::now();
        auto duration = std::chrono::duration_cast<std::chrono::microseconds>(
            endTime - entry.startTime);
        entry.totalTime += duration.count();
        entry.callCount++;
    }
    
    static void printReport() {
        std::cout << "\n=== Performance Report ===" << std::endl;
        for(const auto& [name, entry] : data) {
            double avgTime = static_cast<double>(entry.totalTime) / entry.callCount;
            std::cout << name << ": " 
                      << entry.totalTime << "µs total, "
                      << entry.callCount << " calls, "
                      << avgTime << "µs avg" << std::endl;
        }
    }
};

// Example usage
void expensiveOperation() {
    Profiler::start("expensiveOperation");
    
    // Simulate work
    volatile double result = 0;
    for(int i = 0; i < 1000000; i++) {
        result += i * 0.5;
    }
    
    Profiler::stop("expensiveOperation");
}

int main() {
    for(int i = 0; i < 10; i++) {
        expensiveOperation();
    }
    
    Profiler::printReport();
    return 0;
}

Optimization Techniques:

cpp

// 1. Cache Optimization
class CacheFriendly {
private:
    int* data;
    int size;
    
public:
    // GOOD: Sequential access
    int sum() const {
        int total = 0;
        for(int i = 0; i < size; i++) {
            total += data[i];  // Sequential memory access
        }
        return total;
    }
};

// 2. Avoid Virtual Calls in Hot Paths
class OptimizedBase {
public:
    // Virtual function - indirection cost
    virtual void process() = 0;
    
    // Non-virtual helper
    void optimizedProcess() {
        // Inlineable code
        preProcess();
        process();  // Virtual call
        postProcess();
    }
    
private:
    void preProcess() { /* ... */ }
    void postProcess() { /* ... */ }
};

// 3. Memory Pool Pattern
class MemoryPool {
private:
    struct Block {
        Block* next;
    };
    
    Block* freeList = nullptr;
    size_t blockSize;
    vector<char*> chunks;
    
public:
    MemoryPool(size_t blockSize, size_t initialBlocks)
        : blockSize(blockSize) {
        allocateChunk(initialBlocks);
    }
    
    void* allocate() {
        if(!freeList) {
            allocateChunk(100);  // Allocate more blocks
        }
        
        Block* block = freeList;
        freeList = freeList->next;
        return block;
    }
    
    void deallocate(void* ptr) {
        Block* block = static_cast<Block*>(ptr);
        block->next = freeList;
        freeList = block;
    }
};

// 4. SIMD Optimization (Advanced)
#include <immintrin.h>  // Intel intrinsics

void simdAdd(const float* a, const float* b, float* c, int n) {
    for(int i = 0; i < n; i += 8) {
        __m256 va = _mm256_load_ps(&a[i]);
        __m256 vb = _mm256_load_ps(&b[i]);
        __m256 vc = _mm256_add_ps(va, vb);
        _mm256_store_ps(&c[i], vc);
    }
}

4. Design Patterns (Introduction)

Design patterns are proven solutions to common software design problems.

Singleton Pattern

Ensures a class has only one instance and provides global access.

cpp

#include <iostream>
#include <mutex>

// Modern C++ Thread-safe Singleton (Meyer's Singleton)
class Logger {
private:
    Logger() {
        std::cout << "Logger initialized" << std::endl;
    }
    
    ~Logger() {
        std::cout << "Logger destroyed" << std::endl;
    }
    
    // Delete copy constructor and assignment
    Logger(const Logger&) = delete;
    Logger& operator=(const Logger&) = delete;
    
public:
    static Logger& getInstance() {
        static Logger instance;  // Thread-safe in C++11
        return instance;
    }
    
    void log(const std::string& message) {
        std::cout << "LOG: " << message << std::endl;
    }
    
    void setLogLevel(int level) {
        logLevel = level;
    }
    
private:
    int logLevel = 1;
};

// Thread-safe Singleton with double-checked locking (for older C++)
class ConfigManager {
private:
    static ConfigManager* instance;
    static std::mutex mtx;
    
    ConfigManager() {
        std::cout << "ConfigManager created" << std::endl;
        // Load configuration
    }
    
public:
    static ConfigManager* getInstance() {
        if(instance == nullptr) {  // First check (fast path)
            std::lock_guard<std::mutex> lock(mtx);
            if(instance == nullptr) {  // Second check (inside lock)
                instance = new ConfigManager();
            }
        }
        return instance;
    }
    
    std::string getConfig(const std::string& key) {
        return config[key];
    }
    
    void setConfig(const std::string& key, const std::string& value) {
        config[key] = value;
    }
    
    // Prevent copying
    ConfigManager(const ConfigManager&) = delete;
    ConfigManager& operator=(const ConfigManager&) = delete;
    
private:
    std::map<std::string, std::string> config;
};

// Initialize static members
ConfigManager* ConfigManager::instance = nullptr;
std::mutex ConfigManager::mtx;

// Singleton with template (CRTP)
template<typename T>
class Singleton {
protected:
    Singleton() = default;
    ~Singleton() = default;
    
public:
    static T& getInstance() {
        static T instance;
        return instance;
    }
    
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;
};

class DatabaseConnection : public Singleton<DatabaseConnection> {
    friend class Singleton<DatabaseConnection>;
    
private:
    DatabaseConnection() {
        std::cout << "Database connection established" << std::endl;
    }
    
public:
    void query(const std::string& sql) {
        std::cout << "Executing: " << sql << std::endl;
    }
};

// Usage
int main() {
    // Meyer's Singleton
    Logger::getInstance().log("Application started");
    Logger::getInstance().setLogLevel(2);
    
    // Double-checked locking
    ConfigManager::getInstance()->setConfig("timeout", "30");
    std::string timeout = ConfigManager::getInstance()->getConfig("timeout");
    
    // Template Singleton
    DatabaseConnection::getInstance().query("SELECT * FROM users");
    
    return 0;
}

Factory Pattern

Creates objects without specifying the exact class.

cpp

#include <iostream>
#include <memory>
#include <map>

// Product interface
class Shape {
public:
    virtual ~Shape() = default;
    virtual void draw() const = 0;
    virtual double area() const = 0;
    virtual std::string getName() const = 0;
};

// Concrete products
class Circle : public Shape {
private:
    double radius;
    
public:
    Circle(double r) : radius(r) {}
    
    void draw() const override {
        std::cout << "Drawing Circle with radius " << radius << std::endl;
    }
    
    double area() const override {
        return 3.14159 * radius * radius;
    }
    
    std::string getName() const override {
        return "Circle";
    }
};

class Rectangle : public Shape {
private:
    double width, height;
    
public:
    Rectangle(double w, double h) : width(w), height(h) {}
    
    void draw() const override {
        std::cout << "Drawing Rectangle " << width << "x" << height << std::endl;
    }
    
    double area() const override {
        return width * height;
    }
    
    std::string getName() const override {
        return "Rectangle";
    }
};

class Triangle : public Shape {
private:
    double base, height;
    
public:
    Triangle(double b, double h) : base(b), height(h) {}
    
    void draw() const override {
        std::cout << "Drawing Triangle with base " << base 
                  << " and height " << height << std::endl;
    }
    
    double area() const override {
        return 0.5 * base * height;
    }
    
    std::string getName() const override {
        return "Triangle";
    }
};

// Simple Factory
class ShapeFactory {
public:
    enum ShapeType { CIRCLE, RECTANGLE, TRIANGLE };
    
    static std::unique_ptr<Shape> createShape(ShapeType type, double param1 = 0, double param2 = 0) {
        switch(type) {
            case CIRCLE:
                return std::make_unique<Circle>(param1);
            case RECTANGLE:
                return std::make_unique<Rectangle>(param1, param2);
            case TRIANGLE:
                return std::make_unique<Triangle>(param1, param2);
            default:
                throw std::invalid_argument("Unknown shape type");
        }
    }
};

// Factory Method Pattern
class Document {
public:
    virtual ~Document() = default;
    virtual void open() = 0;
    virtual void save() = 0;
    virtual std::string getType() const = 0;
};

class TextDocument : public Document {
public:
    void open() override {
        std::cout << "Opening Text Document" << std::endl;
    }
    
    void save() override {
        std::cout << "Saving Text Document" << std::endl;
    }
    
    std::string getType() const override {
        return "Text";
    }
};

class SpreadsheetDocument : public Document {
public:
    void open() override {
        std::cout << "Opening Spreadsheet" << std::endl;
    }
    
    void save() override {
        std::cout << "Saving Spreadsheet" << std::endl;
    }
    
    std::string getType() const override {
        return "Spreadsheet";
    }
};

class Application {
public:
    virtual ~Application() = default;
    virtual std::unique_ptr<Document> createDocument() = 0;
    
    void newDocument() {
        auto doc = createDocument();
        doc->open();
        documents.push_back(std::move(doc));
    }
    
    void listDocuments() const {
        std::cout << "Documents: " << std::endl;
        for(const auto& doc : documents) {
            std::cout << "  - " << doc->getType() << std::endl;
        }
    }
    
private:
    std::vector<std::unique_ptr<Document>> documents;
};

class TextEditor : public Application {
public:
    std::unique_ptr<Document> createDocument() override {
        return std::make_unique<TextDocument>();
    }
};

class SpreadsheetApp : public Application {
public:
    std::unique_ptr<Document> createDocument() override {
        return std::make_unique<SpreadsheetDocument>();
    }
};

// Abstract Factory Pattern
class Widget {
public:
    virtual ~Widget() = default;
    virtual void render() const = 0;
};

class Button : public Widget {
public:
    virtual void onClick() const = 0;
};

class Checkbox : public Widget {
public:
    virtual void onCheck() const = 0;
};

// Concrete products for Windows
class WindowsButton : public Button {
public:
    void render() const override {
        std::cout << "Rendering Windows-style button" << std::endl;
    }
    
    void onClick() const override {
        std::cout << "Windows button clicked" << std::endl;
    }
};

class WindowsCheckbox : public Checkbox {
public:
    void render() const override {
        std::cout << "Rendering Windows-style checkbox" << std::endl;
    }
    
    void onCheck() const override {
        std::cout << "Windows checkbox checked" << std::endl;
    }
};

// Concrete products for macOS
class MacButton : public Button {
public:
    void render() const override {
        std::cout << "Rendering macOS-style button" << std::endl;
    }
    
    void onClick() const override {
        std::cout << "macOS button clicked" << std::endl;
    }
};

class MacCheckbox : public Checkbox {
public:
    void render() const override {
        std::cout << "Rendering macOS-style checkbox" << std::endl;
    }
    
    void onCheck() const override {
        std::cout << "macOS checkbox checked" << std::endl;
    }
};

// Abstract Factory
class GUIFactory {
public:
    virtual ~GUIFactory() = default;
    virtual std::unique_ptr<Button> createButton() const = 0;
    virtual std::unique_ptr<Checkbox> createCheckbox() const = 0;
};

// Concrete factories
class WindowsFactory : public GUIFactory {
public:
    std::unique_ptr<Button> createButton() const override {
        return std::make_unique<WindowsButton>();
    }
    
    std::unique_ptr<Checkbox> createCheckbox() const override {
        return std::make_unique<WindowsCheckbox>();
    }
};

class MacFactory : public GUIFactory {
public:
    std::unique_ptr<Button> createButton() const override {
        return std::make_unique<MacButton>();
    }
    
    std::unique_ptr<Checkbox> createCheckbox() const override {
        return std::make_unique<MacCheckbox>();
    }
};

// Client code
class ApplicationUI {
private:
    std::unique_ptr<GUIFactory> factory;
    
public:
    ApplicationUI(std::unique_ptr<GUIFactory> f) : factory(std::move(f)) {}
    
    void createUI() {
        auto button = factory->createButton();
        auto checkbox = factory->createCheckbox();
        
        button->render();
        button->onClick();
        
        checkbox->render();
        checkbox->onCheck();
    }
};

// Usage
int main() {
    // Simple Factory
    auto circle = ShapeFactory::createShape(ShapeFactory::CIRCLE, 5.0);
    auto rect = ShapeFactory::createShape(ShapeFactory::RECTANGLE, 4.0, 6.0);
    
    circle->draw();
    std::cout << "Circle area: " << circle->area() << std::endl;
    
    // Factory Method
    TextEditor editor;
    editor.newDocument();
    editor.newDocument();
    editor.listDocuments();
    
    // Abstract Factory
#ifdef _WIN32
    auto factory = std::make_unique<WindowsFactory>();
#elif __APPLE__
    auto factory = std::make_unique<MacFactory>();
#endif
    
    ApplicationUI app(std::move(factory));
    app.createUI();
    
    return 0;
}

Observer Pattern

Defines a one-to-many dependency between objects so that when one object changes state, all its dependents are notified.

cpp

#include <iostream>
#include <vector>
#include <memory>
#include <algorithm>
#include <string>

// Observer interface
class Observer {
public:
    virtual ~Observer() = default;
    virtual void update(const std::string& message) = 0;
    virtual std::string getName() const = 0;
};

// Subject (Observable) interface
class Subject {
public:
    virtual ~Subject() = default;
    virtual void attach(std::shared_ptr<Observer> observer) = 0;
    virtual void detach(std::shared_ptr<Observer> observer) = 0;
    virtual void notify(const std::string& message) = 0;
};

// Concrete Subject
class WeatherStation : public Subject {
private:
    std::vector<std::shared_ptr<Observer>> observers;
    float temperature;
    float humidity;
    float pressure;
    
public:
    void attach(std::shared_ptr<Observer> observer) override {
        observers.push_back(observer);
        std::cout << observer->getName() << " subscribed to weather updates" << std::endl;
    }
    
    void detach(std::shared_ptr<Observer> observer) override {
        auto it = std::find(observers.begin(), observers.end(), observer);
        if(it != observers.end()) {
            observers.erase(it);
            std::cout << observer->getName() << " unsubscribed from weather updates" << std::endl;
        }
    }
    
    void notify(const std::string& message) override {
        for(const auto& observer : observers) {
            observer->update(message);
        }
    }
    
    void setMeasurements(float temp, float hum, float press) {
        temperature = temp;
        humidity = hum;
        pressure = press;
        
        std::string message = "Weather updated: T=" + std::to_string(temp) 
                            + "°C, H=" + std::to_string(hum) 
                            + "%, P=" + std::to_string(press) + "hPa";
        
        measurementsChanged();
        notify(message);
    }
    
private:
    void measurementsChanged() {
        // Could add validation or processing here
        std::cout << "\nWeather measurements changed" << std::endl;
    }
};

// Concrete Observers
class PhoneDisplay : public Observer {
private:
    std::string name;
    
public:
    PhoneDisplay(const std::string& n) : name(n) {}
    
    void update(const std::string& message) override {
        std::cout << "[Phone Display - " << name << "] " << message << std::endl;
    }
    
    std::string getName() const override {
        return name;
    }
};

class DesktopDisplay : public Observer {
private:
    std::string name;
    
public:
    DesktopDisplay(const std::string& n) : name(n) {}
    
    void update(const std::string& message) override {
        std::cout << "[Desktop Display - " << name << "] " << message << std::endl;
    }
    
    std::string getName() const override {
        return name;
    }
};

class Logger : public Observer {
private:
    std::string filename;
    
public:
    Logger(const std::string& file) : filename(file) {}
    
    void update(const std::string& message) override {
        // Simulate logging to file
        std::cout << "[Logger] Writing to " << filename << ": " << message << std::endl;
    }
    
    std::string getName() const override {
        return "Logger";
    }
};

// Modern C++ Observer with templates and std::function
template<typename T>
class Observable {
private:
    std::vector<std::function<void(const T&)>> observers;
    
public:
    void subscribe(std::function<void(const T&)> observer) {
        observers.push_back(observer);
    }
    
    void unsubscribe(std::function<void(const T&)> observer) {
        observers.erase(
            std::remove(observers.begin(), observers.end(), observer),
            observers.end()
        );
    }
    
    void notify(const T& data) {
        for(const auto& observer : observers) {
            observer(data);
        }
    }
};

class Stock {
public:
    std::string symbol;
    double price;
    
    Stock(const std::string& sym, double pr) : symbol(sym), price(pr) {}
};

class StockMarket : public Observable<Stock> {
public:
    void updateStockPrice(const std::string& symbol, double newPrice) {
        Stock stock(symbol, newPrice);
        notify(stock);
    }
};

// Usage
int main() {
    // Classic Observer Pattern
    std::cout << "=== Weather Station Observer ===" << std::endl;
    
    auto weatherStation = std::make_shared<WeatherStation>();
    
    auto phone1 = std::make_shared<PhoneDisplay>("John's iPhone");
    auto phone2 = std::make_shared<PhoneDisplay>("Jane's Android");
    auto desktop = std::make_shared<DesktopDisplay>("Office PC");
    auto logger = std::make_shared<Logger>("weather_log.txt");
    
    weatherStation->attach(phone1);
    weatherStation->attach(phone2);
    weatherStation->attach(desktop);
    weatherStation->attach(logger);
    
    weatherStation->setMeasurements(25.5, 60, 1013);
    
    weatherStation->detach(phone2);
    weatherStation->setMeasurements(26.0, 58, 1012);
    
    // Modern Observer Pattern
    std::cout << "\n=== Stock Market Observer ===" << std::endl;
    
    StockMarket market;
    
    // Lambda observers
    auto displayObserver = [](const Stock& stock) {
        std::cout << "[Display] " << stock.symbol << ": $" << stock.price << std::endl;
    };
    
    auto alertObserver = [](const Stock& stock) {
        if(stock.price > 150) {
            std::cout << "[Alert] " << stock.symbol << " is over $150!" << std::endl;
        }
    };
    
    auto logObserver = [](const Stock& stock) {
        std::cout << "[Log] " << stock.symbol << " price updated to " << stock.price << std::endl;
    };
    
    market.subscribe(displayObserver);
    market.subscribe(alertObserver);
    market.subscribe(logObserver);
    
    market.updateStockPrice("AAPL", 145.67);
    market.updateStockPrice("GOOGL", 2750.50);
    market.updateStockPrice("TSLA", 155.30);
    
    // Unsubscribe
    market.unsubscribe(logObserver);
    market.updateStockPrice("MSFT", 330.25);
    
    return 0;
}

Strategy Pattern

Defines a family of algorithms, encapsulates each one, and makes them interchangeable.

cpp

#include <iostream>
#include <memory>
#include <vector>
#include <functional>

// Strategy interface
class PaymentStrategy {
public:
    virtual ~PaymentStrategy() = default;
    virtual bool pay(double amount) = 0;
    virtual std::string getName() const = 0;
};

// Concrete strategies
class CreditCardPayment : public PaymentStrategy {
private:
    std::string cardNumber;
    std::string expiryDate;
    std::string cvv;
    
public:
    CreditCardPayment(const std::string& num, const std::string& exp, const std::string& c)
        : cardNumber(num), expiryDate(exp), cvv(c) {}
    
    bool pay(double amount) override {
        std::cout << "Processing credit card payment of $" << amount 
                  << " with card ending in " 
                  << cardNumber.substr(cardNumber.length() - 4) 
                  << std::endl;
        
        // Simulate payment processing
        if(validateCard()) {
            std::cout << "Payment successful!" << std::endl;
            return true;
        } else {
            std::cout << "Payment failed: Invalid card" << std::endl;
            return false;
        }
    }
    
    std::string getName() const override {
        return "Credit Card";
    }
    
private:
    bool validateCard() const {
        // Simplified validation
        return cardNumber.length() == 16 && cvv.length() == 3;
    }
};

class PayPalPayment : public PaymentStrategy {
private:
    std::string email;
    
public:
    PayPalPayment(const std::string& e) : email(e) {}
    
    bool pay(double amount) override {
        std::cout << "Processing PayPal payment of $" << amount 
                  << " for account " << email << std::endl;

Comments (0)

No comments yet. Be the first to comment!

LecturreAI Assistant

Hello! I'm LecturreAI

What would you like to learn today?