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:
-
Includes:
#includeinserts header files -
Macros:
#defineexpansions -
Conditional Compilation:
#ifdef,#if,#endif -
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:
-
Symbol Resolution: Matches references with definitions
-
Relocation: Adjusts addresses
-
Library Linking: Links static/dynamic libraries
-
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!