Skip to content

Advanced Modern C++ Programming

Advanced Modern C++ Programming: A Comprehensive University Course

Table of Contents

  1. Introduction to Modern C++
  2. Memory Model and Pointers
  3. Dynamic Memory Management
  4. Smart Pointers and Modern Memory Management
  5. Template Fundamentals
  6. Advanced Template Techniques
  7. Object-Oriented Programming Advanced Concepts
  8. Modern C++ Features
  9. Exception Handling and RAII
  10. Concurrency and Threading
  11. Best Practices and Design Patterns
  12. Performance Optimization
  13. Advanced Topics and Modern Features
  14. Complete Application Example
  15. Conclusion and Resources

Introduction to Modern C++

Course Overview

This comprehensive tutorial covers advanced C++ programming concepts essential for professional software development. We’ll explore memory management, pointers, templates, and modern C++ features (C++11/14/17/20) with a focus on best practices and real-world applications.

Prerequisites

Before starting this course, you should have:

  • Basic understanding of programming concepts
  • Familiarity with C or basic C++ syntax
  • Understanding of compilation process
  • Basic knowledge of computer memory organization

Modern C++ Evolution

C++ Standards Timeline:

  • C++98/03: Classical C++
  • C++11: Modern C++ begins (auto, lambdas, smart pointers)
  • C++14: Refinements and improvements
  • C++17: Structured bindings, std::optional
  • C++20: Concepts, modules, coroutines
  • C++23: Latest standard

Memory Model and Pointers

Understanding Memory Layout

C++ programs organize memory into several segments:

Memory Segments:

  • Stack: Local variables, function parameters, return addresses
  • Heap: Dynamically allocated memory
  • Data Segment: Global and static variables
  • Code Segment: Program instructions

Pointer Fundamentals

Basic Pointer Concepts

A pointer is a variable that stores the memory address of another variable.

#include <iostream>
int main() {
int value = 42;
int* ptr = &value; // ptr stores address of value
std::cout << "Value: " << value << std::endl;
std::cout << "Address of value: " << &value << std::endl;
std::cout << "Pointer value: " << ptr << std::endl;
std::cout << "Dereferenced pointer: " << *ptr << std::endl;
return 0;
}

Pointer Arithmetic

Pointers support arithmetic operations that are scaled by the size of the pointed-to type:

#include <iostream>
int main() {
int arr[] = {10, 20, 30, 40, 50};
int* ptr = arr; // Points to first element
for (int i = 0; i < 5; ++i) {
std::cout << "arr[" << i << "] = " << *(ptr + i) << std::endl;
// Equivalent to: std::cout << ptr[i] << std::endl;
}
return 0;
}

Pointer to Pointer

Multi-level indirection allows pointers to point to other pointers:

#include <iostream>
int main() {
int value = 100;
int* ptr1 = &value;
int** ptr2 = &ptr1; // Pointer to pointer
std::cout << "Value: " << value << std::endl;
std::cout << "Via ptr1: " << *ptr1 << std::endl;
std::cout << "Via ptr2: " << **ptr2 << std::endl;
// Modifying value through double pointer
**ptr2 = 200;
std::cout << "Modified value: " << value << std::endl;
return 0;
}

References vs Pointers

Key Differences:

  • References: Must be initialized, cannot be reassigned, no null references
  • Pointers: Can be uninitialized, can be reassigned, can be null
#include <iostream>
void demonstrateReferences() {
int x = 10, y = 20;
// Reference must be initialized
int& ref = x;
int* ptr = &x;
std::cout << "Initial - x: " << x << ", ref: " << ref
<< ", *ptr: " << *ptr << std::endl;
// Reference cannot be reassigned (this modifies x)
ref = y; // This assigns y's value to x, doesn't reassign ref
// Pointer can be reassigned
ptr = &y;
std::cout << "After - x: " << x << ", ref: " << ref
<< ", *ptr: " << *ptr << std::endl;
}

Function Pointers

Function pointers enable dynamic function calls and callback mechanisms:

#include <iostream>
// Function declarations
int add(int a, int b) { return a + b; }
int subtract(int a, int b) { return a - b; }
int multiply(int a, int b) { return a * b; }
// Function that takes function pointer as parameter
int calculate(int x, int y, int (*operation)(int, int)) {
return operation(x, y);
}
int main() {
// Array of function pointers
int (*operations[])(int, int) = {add, subtract, multiply};
const char* names[] = {"add", "subtract", "multiply"};
int a = 10, b = 5;
for (int i = 0; i < 3; ++i) {
int result = calculate(a, b, operations[i]);
std::cout << names[i] << "(" << a << ", " << b
<< ") = " << result << std::endl;
}
return 0;
}

Dynamic Memory Management

The Heap and Dynamic Allocation

Dynamic memory allocation allows programs to request memory at runtime:

⚠️ Memory Management Responsibility: With great power comes great responsibility. Every new must have a corresponding delete, and every new[] must have a corresponding delete[].

Basic Dynamic Allocation

#include <iostream>
int main() {
// Single object allocation
int* ptr = new int(42);
std::cout << "Dynamically allocated value: " << *ptr << std::endl;
delete ptr; // Must free memory
ptr = nullptr; // Good practice to avoid dangling pointers
// Array allocation
int size = 5;
int* arr = new int[size]{1, 2, 3, 4, 5};
for (int i = 0; i < size; ++i) {
std::cout << "arr[" << i << "] = " << arr[i] << std::endl;
}
delete[] arr; // Use delete[] for arrays
arr = nullptr;
return 0;
}

Dynamic 2D Arrays

#include <iostream>
class Matrix {
private:
int** data;
int rows, cols;
public:
Matrix(int r, int c) : rows(r), cols(c) {
// Allocate array of pointers
data = new int*[rows];
// Allocate each row
for (int i = 0; i < rows; ++i) {
data[i] = new int[cols](); // Initialize to zero
}
}
~Matrix() {
// Deallocate each row
for (int i = 0; i < rows; ++i) {
delete[] data[i];
}
// Deallocate array of pointers
delete[] data;
}
int& operator()(int row, int col) {
return data[row][col];
}
void print() const {
for (int i = 0; i < rows; ++i) {
for (int j = 0; j < cols; ++j) {
std::cout << data[i][j] << " ";
}
std::cout << std::endl;
}
}
};
int main() {
Matrix mat(3, 4);
// Fill matrix
for (int i = 0; i < 3; ++i) {
for (int j = 0; j < 4; ++j) {
mat(i, j) = i * 4 + j + 1;
}
}
mat.print();
return 0;
}

Memory Leaks and Common Pitfalls

⚠️ Common Memory Management Errors:

  • Memory leaks (forgetting to delete)
  • Double deletion
  • Dangling pointers
  • Buffer overruns
  • Using deleted memory

RAII (Resource Acquisition Is Initialization)

RAII is a fundamental C++ idiom for resource management:

RAII Principles:

  • Resources are acquired in constructor
  • Resources are released in destructor
  • Object lifetime manages resource lifetime
  • Exception safety is automatic
#include <iostream>
class RAIIExample {
private:
int* data;
size_t size;
public:
// Constructor acquires resource
RAIIExample(size_t s) : size(s) {
data = new int[size];
std::cout << "Allocated array of size " << size << std::endl;
}
// Destructor releases resource
~RAIIExample() {
delete[] data;
std::cout << "Deallocated array" << std::endl;
}
// Copy constructor (deep copy)
RAIIExample(const RAIIExample& other) : size(other.size) {
data = new int[size];
for (size_t i = 0; i < size; ++i) {
data[i] = other.data[i];
}
}
// Assignment operator
RAIIExample& operator=(const RAIIExample& other) {
if (this != &other) {
delete[] data; // Release old resource
size = other.size;
data = new int[size]; // Acquire new resource
for (size_t i = 0; i < size; ++i) {
data[i] = other.data[i];
}
}
return *this;
}
int& operator[](size_t index) { return data[index]; }
const int& operator[](size_t index) const { return data[index]; }
};
int main() {
RAIIExample arr(10);
for (int i = 0; i < 10; ++i) {
arr[i] = i * i;
}
// Resource automatically released when arr goes out of scope
return 0;
}

Smart Pointers and Modern Memory Management

Introduction to Smart Pointers

Smart pointers automatically manage memory and provide exception safety:

Smart Pointer Types:

  • std::unique_ptr: Exclusive ownership
  • std::shared_ptr: Shared ownership with reference counting
  • std::weak_ptr: Non-owning observer to break cycles

std::unique_ptr

unique_ptr provides exclusive ownership semantics:

#include <iostream>
#include <memory>
class Resource {
public:
Resource(int id) : id_(id) {
std::cout << "Resource " << id_ << " created" << std::endl;
}
~Resource() {
std::cout << "Resource " << id_ << " destroyed" << std::endl;
}
void doSomething() {
std::cout << "Resource " << id_ << " doing something" << std::endl;
}
private:
int id_;
};
std::unique_ptr<Resource> createResource(int id) {
return std::make_unique<Resource>(id);
}
int main() {
// Creating unique_ptr
auto ptr1 = std::make_unique<Resource>(1);
ptr1->doSomething();
// Moving ownership
auto ptr2 = std::move(ptr1);
// ptr1 is now nullptr
if (!ptr1) {
std::cout << "ptr1 is empty after move" << std::endl;
}
ptr2->doSomething();
// Array version
auto arr = std::make_unique<int[]>(10);
for (int i = 0; i < 10; ++i) {
arr[i] = i * i;
}
// Resources automatically cleaned up
return 0;
}

std::shared_ptr

shared_ptr enables shared ownership with reference counting:

#include <iostream>
#include <memory>
#include <vector>
class Node {
public:
Node(int value) : value_(value) {
std::cout << "Node " << value_ << " created" << std::endl;
}
~Node() {
std::cout << "Node " << value_ << " destroyed" << std::endl;
}
void addChild(std::shared_ptr<Node> child) {
children_.push_back(child);
}
void print(int depth = 0) const {
for (int i = 0; i < depth; ++i) std::cout << " ";
std::cout << "Node " << value_ << " (refs: "
<< children_.size() << ")" << std::endl;
for (const auto& child : children_) {
child->print(depth + 1);
}
}
private:
int value_;
std::vector<std::shared_ptr<Node>> children_;
};
int main() {
auto root = std::make_shared<Node>(0);
auto child1 = std::make_shared<Node>(1);
auto child2 = std::make_shared<Node>(2);
auto grandchild = std::make_shared<Node>(3);
root->addChild(child1);
root->addChild(child2);
child1->addChild(grandchild);
child2->addChild(grandchild); // grandchild has 2 parents
std::cout << "Reference count for grandchild: "
<< grandchild.use_count() << std::endl;
root->print();
// All nodes destroyed automatically when ref count reaches 0
return 0;
}

std::weak_ptr and Circular References

weak_ptr helps break circular references:

#include <iostream>
#include <memory>
class Parent;
class Child;
class Parent {
public:
Parent(const std::string& name) : name_(name) {
std::cout << "Parent " << name_ << " created" << std::endl;
}
~Parent() {
std::cout << "Parent " << name_ << " destroyed" << std::endl;
}
void addChild(std::shared_ptr<Child> child) {
children_.push_back(child);
}
std::string getName() const { return name_; }
private:
std::string name_;
std::vector<std::shared_ptr<Child>> children_;
};
class Child {
public:
Child(const std::string& name) : name_(name) {
std::cout << "Child " << name_ << " created" << std::endl;
}
~Child() {
std::cout << "Child " << name_ << " destroyed" << std::endl;
}
void setParent(std::shared_ptr<Parent> parent) {
parent_ = parent; // weak_ptr doesn't increase ref count
}
void printParent() const {
if (auto parent = parent_.lock()) { // Convert to shared_ptr
std::cout << "Child " << name_ << " has parent "
<< parent->getName() << std::endl;
} else {
std::cout << "Child " << name_ << " has no parent" << std::endl;
}
}
private:
std::string name_;
std::weak_ptr<Parent> parent_; // Weak reference to avoid cycle
};
int main() {
auto parent = std::make_shared<Parent>("John");
auto child1 = std::make_shared<Child>("Alice");
auto child2 = std::make_shared<Child>("Bob");
parent->addChild(child1);
parent->addChild(child2);
child1->setParent(parent);
child2->setParent(parent);
child1->printParent();
child2->printParent();
// No circular reference, all objects destroyed properly
return 0;
}

Template Fundamentals

Introduction to Templates

Templates enable generic programming by allowing functions and classes to work with different types:

Template Benefits:

  • Code reuse without sacrificing type safety
  • Compile-time polymorphism
  • No runtime overhead
  • Highly optimized code generation

Function Templates

Basic Function Templates

#include <iostream>
#include <string>
#include <cstring>
// Basic function template
template<typename T>
T maximum(T a, T b) {
return (a > b) ? a : b;
}
// Template with multiple parameters
template<typename T, typename U>
auto add(T a, U b) -> decltype(a + b) {
return a + b;
}
// Template specialization
template<>
const char* maximum<const char*>(const char* a, const char* b) {
return (std::strcmp(a, b) > 0) ? a : b;
}
int main() {
// Template argument deduction
std::cout << "Max of 10, 20: " << maximum(10, 20) << std::endl;
std::cout << "Max of 3.14, 2.71: " << maximum(3.14, 2.71) << std::endl;
// Explicit template arguments
std::cout << "Max of 'hello', 'world': "
<< maximum<const char*>("hello", "world") << std::endl;
// Mixed types
std::cout << "Add 5 + 3.14: " << add(5, 3.14) << std::endl;
return 0;
}

Advanced Function Templates

#include <iostream>
#include <vector>
#include <algorithm>
#include <type_traits>
// Template with default parameters
template<typename T, typename Compare = std::less<T>>
void bubbleSort(std::vector<T>& vec, Compare comp = Compare{}) {
for (size_t i = 0; i < vec.size(); ++i) {
for (size_t j = 0; j < vec.size() - 1 - i; ++j) {
if (comp(vec[j + 1], vec[j])) {
std::swap(vec[j], vec[j + 1]);
}
}
}
}
// SFINAE (Substitution Failure Is Not An Error)
template<typename T>
typename std::enable_if<std::is_arithmetic<T>::value, T>::type
safeDivide(T a, T b) {
if (b == 0) {
throw std::runtime_error("Division by zero");
}
return a / b;
}
// Variadic templates
template<typename T>
T sum(T value) {
return value;
}
template<typename T, typename... Args>
T sum(T first, Args... args) {
return first + sum(args...);
}
int main() {
std::vector<int> numbers = {64, 34, 25, 12, 22, 11, 90};
bubbleSort(numbers);
std::cout << "Sorted numbers: ";
for (int num : numbers) {
std::cout << num << " ";
}
std::cout << std::endl;
// SFINAE example
try {
std::cout << "10 / 3 = " << safeDivide(10, 3) << std::endl;
std::cout << "10.0 / 0.0 = " << safeDivide(10.0, 0.0) << std::endl;
} catch (const std::exception& e) {
std::cout << "Error: " << e.what() << std::endl;
}
// Variadic templates
std::cout << "Sum: " << sum(1, 2, 3, 4, 5) << std::endl;
std::cout << "Sum: " << sum(1.1, 2.2, 3.3) << std::endl;
return 0;
}

Class Templates

Basic Class Templates

#include <iostream>
#include <stdexcept>
template<typename T, size_t N>
class Array {
private:
T data_[N];
public:
// Constructor
Array() = default;
// Copy constructor
Array(const Array& other) {
for (size_t i = 0; i < N; ++i) {
data_[i] = other.data_[i];
}
}
// Assignment operator
Array& operator=(const Array& other) {
if (this != &other) {
for (size_t i = 0; i < N; ++i) {
data_[i] = other.data_[i];
}
}
return *this;
}
// Element access
T& operator[](size_t index) {
if (index >= N) {
throw std::out_of_range("Index out of range");
}
return data_[index];
}
const T& operator[](size_t index) const {
if (index >= N) {
throw std::out_of_range("Index out of range");
}
return data_[index];
}
// Size
constexpr size_t size() const { return N; }
// Iterators
T* begin() { return data_; }
T* end() { return data_ + N; }
const T* begin() const { return data_; }
const T* end() const { return data_ + N; }
};
int main() {
Array<int, 5> intArray;
Array<double, 3> doubleArray;
// Fill arrays
for (size_t i = 0; i < intArray.size(); ++i) {
intArray[i] = i * i;
}
for (size_t i = 0; i < doubleArray.size(); ++i) {
doubleArray[i] = i * 3.14;
}
// Print arrays
std::cout << "Int array: ";
for (const auto& val : intArray) {
std::cout << val << " ";
}
std::cout << std::endl;
std::cout << "Double array: ";
for (const auto& val : doubleArray) {
std::cout << val << " ";
}
std::cout << std::endl;
return 0;
}

Advanced Template Techniques

Template Metaprogramming

Template metaprogramming enables compile-time computation and code generation:

#include <iostream>
#include <type_traits>
#include <array>
// Compile-time factorial
template<int N>
struct Factorial {
static constexpr int value = N * Factorial<N - 1>::value;
};
template<>
struct Factorial<0> {
static constexpr int value = 1;
};
// SFINAE and type traits
template<typename T>
class has_size_method {
private:
template<typename U>
static auto check(U*) -> decltype(std::declval<U>().size(), std::true_type{});
template<typename>
static std::false_type check(...);
public:
static constexpr bool value = decltype(check<T>(nullptr))::value;
};
// Template for containers with size() method
template<typename Container>
typename std::enable_if<has_size_method<Container>::value, size_t>::type
getSize(const Container& c) {
return c.size();
}
// Template for arrays
template<typename T, size_t N>
constexpr size_t getSize(const T (&)[N]) {
return N;
}
// Variadic template for tuple-like operations
template<typename... Types>
class TypeList {
public:
static constexpr size_t size = sizeof...(Types);
template<size_t Index>
using type_at = typename std::tuple_element<Index, std::tuple<Types...>>::type;
};
// Template recursion for printing types
template<typename T>
void printType() {
std::cout << typeid(T).name() << std::endl;
}
template<typename First, typename... Rest>
void printTypes() {
printType<First>();
if constexpr (sizeof...(Rest) > 0) {
printTypes<Rest...>();
}
}
int main() {
// Compile-time computation
constexpr int fact5 = Factorial<5>::value;
std::cout << "5! = " << fact5 << std::endl;
// SFINAE with containers
std::vector<int> vec = {1, 2, 3, 4, 5};
std::array<int, 3> arr = {1, 2, 3};
int carray[] = {1, 2, 3, 4};
std::cout << "Vector size: " << getSize(vec) << std::endl;
std::cout << "Array size: " << getSize(arr) << std::endl;
std::cout << "C-array size: " << getSize(carray) << std::endl;
// Type list
using MyTypes = TypeList<int, double, std::string>;
std::cout << "Type list size: " << MyTypes::size << std::endl;
// Print types
std::cout << "Types in list:" << std::endl;
printTypes<int, double, std::string>();
return 0;
}

Template Specialization

#include <iostream>
#include <vector>
#include <string>
#include <memory>
// Primary template
template<typename T>
class SmartContainer {
private:
std::vector<T> data_;
public:
void add(const T& item) {
data_.push_back(item);
}
void print() const {
for (const auto& item : data_) {
std::cout << item << " ";
}
std::cout << std::endl;
}
size_t size() const { return data_.size(); }
};
// Partial specialization for pointers
template<typename T>
class SmartContainer<T*> {
private:
std::vector<std::unique_ptr<T>> data_;
public:
void add(T* item) {
data_.push_back(std::unique_ptr<T>(item));
}
void print() const {
for (const auto& item : data_) {
std::cout << *item << " ";
}
std::cout << std::endl;
}
size_t size() const { return data_.size(); }
};
// Full specialization for std::string
template<>
class SmartContainer<std::string> {
private:
std::vector<std::string> data_;
public:
void add(const std::string& item) {
data_.push_back("[" + item + "]"); // Add brackets
}
void print() const {
for (const auto& item : data_) {
std::cout << item << " ";
}
std::cout << std::endl;
}
size_t size() const { return data_.size(); }
};
int main() {
// Primary template
SmartContainer<int> intContainer;
intContainer.add(1);
intContainer.add(2);
intContainer.add(3);
std::cout << "Int container: ";
intContainer.print();
// Pointer specialization
SmartContainer<int*> ptrContainer;
ptrContainer.add(new int(10));
ptrContainer.add(new int(20));
ptrContainer.add(new int(30));
std::cout << "Pointer container: ";
ptrContainer.print();
// String specialization
SmartContainer<std::string> stringContainer;
stringContainer.add("Hello");
stringContainer.add("World");
stringContainer.add("C++");
std::cout << "String container: ";
stringContainer.print();
return 0;
}

Concepts (C++20)

#include <iostream>
#include <concepts>
#include <vector>
#include <string>
// Define concepts
template<typename T>
concept Numeric = std::integral<T> || std::floating_point<T>;
template<typename T>
concept Printable = requires(T t) {
std::cout << t;
};
template<typename T>
concept Container = requires(T t) {
t.begin();
t.end();
t.size();
};
// Function templates using concepts
template<Numeric T>
T multiply(T a, T b) {
return a * b;
}
template<Printable T>
void print(const T& item) {
std::cout << "Printing: " << item << std::endl;
}
template<Container C>
void printContainer(const C& container) {
std::cout << "Container contents: ";
for (const auto& item : container) {
std::cout << item << " ";
}
std::cout << "(size: " << container.size() << ")" << std::endl;
}
// Concept with multiple requirements
template<typename T>
concept Arithmetic = requires(T a, T b) {
a + b;
a - b;
a * b;
a / b;
} && std::is_arithmetic_v<T>;
template<Arithmetic T>
T calculate(T a, T b) {
return (a + b) * (a - b) / 2;
}
int main() {
// Numeric concept
std::cout << "Multiply integers: " << multiply(5, 6) << std::endl;
std::cout << "Multiply doubles: " << multiply(3.14, 2.0) << std::endl;
// Printable concept
print(42);
print(std::string("Hello, Concepts!"));
print(3.14159);
// Container concept
std::vector<int> vec = {1, 2, 3, 4, 5};
std::string str = "Hello";
printContainer(vec);
printContainer(str);
// Arithmetic concept
std::cout << "Calculate: " << calculate(10, 4) << std::endl;
std::cout << "Calculate: " << calculate(5.5, 2.5) << std::endl;
return 0;
}

Object-Oriented Programming Advanced Concepts

Inheritance and Polymorphism

Virtual Functions and Dynamic Dispatch

#include <iostream>
#include <vector>
#include <memory>
// Base class with virtual functions
class Shape {
protected:
std::string name_;
public:
Shape(const std::string& name) : name_(name) {}
// Virtual destructor is crucial for proper cleanup
virtual ~Shape() = default;
// Pure virtual function makes this an abstract class
virtual double area() const = 0;
virtual double perimeter() const = 0;
// Virtual function with default implementation
virtual void display() const {
std::cout << "Shape: " << name_ << std::endl;
std::cout << "Area: " << area() << std::endl;
std::cout << "Perimeter: " << perimeter() << std::endl;
}
const std::string& getName() const { return name_; }
};
class Rectangle : public Shape {
private:
double width_, height_;
public:
Rectangle(double width, double height)
: Shape("Rectangle"), width_(width), height_(height) {}
double area() const override {
return width_ * height_;
}
double perimeter() const override {
return 2 * (width_ + height_);
}
void display() const override {
Shape::display(); // Call base class implementation
std::cout << "Dimensions: " << width_ << " x " << height_ << std::endl;
}
};
class Circle : public Shape {
private:
double radius_;
static constexpr double PI = 3.14159265359;
public:
Circle(double radius) : Shape("Circle"), radius_(radius) {}
double area() const override {
return PI * radius_ * radius_;
}
double perimeter() const override {
return 2 * PI * radius_;
}
void display() const override {
Shape::display();
std::cout << "Radius: " << radius_ << std::endl;
}
};
class Triangle : public Shape {
private:
double a_, b_, c_; // Side lengths
public:
Triangle(double a, double b, double c)
: Shape("Triangle"), a_(a), b_(b), c_(c) {}
double area() const override {
// Using Heron's formula
double s = perimeter() / 2.0;
return std::sqrt(s * (s - a_) * (s - b_) * (s - c_));
}
double perimeter() const override {
return a_ + b_ + c_;
}
void display() const override {
Shape::display();
std::cout << "Sides: " << a_ << ", " << b_ << ", " << c_ << std::endl;
}
};
int main() {
// Polymorphic container
std::vector<std::unique_ptr<Shape>> shapes;
shapes.push_back(std::make_unique<Rectangle>(5.0, 3.0));
shapes.push_back(std::make_unique<Circle>(2.5));
shapes.push_back(std::make_unique<Triangle>(3.0, 4.0, 5.0));
std::cout << "=== Shape Information ===" << std::endl;
for (const auto& shape : shapes) {
shape->display();
std::cout << "---" << std::endl;
}
// Calculate total area
double totalArea = 0.0;
for (const auto& shape : shapes) {
totalArea += shape->area();
}
std::cout << "Total area of all shapes: " << totalArea << std::endl;
return 0;
}

Multiple Inheritance and Virtual Inheritance

#include <iostream>
#include <string>
// Base classes
class Flyable {
public:
virtual ~Flyable() = default;
virtual void fly() const {
std::cout << "Flying through the air!" << std::endl;
}
virtual double getMaxAltitude() const = 0;
};
class Swimmable {
public:
virtual ~Swimmable() = default;
virtual void swim() const {
std::cout << "Swimming through water!" << std::endl;
}
virtual double getMaxDepth() const = 0;
};
// Common base class that might cause diamond problem
class Animal {
protected:
std::string name_;
int age_;
public:
Animal(const std::string& name, int age) : name_(name), age_(age) {
std::cout << "Animal constructor: " << name_ << std::endl;
}
virtual ~Animal() {
std::cout << "Animal destructor: " << name_ << std::endl;
}
virtual void makeSound() const = 0;
const std::string& getName() const { return name_; }
int getAge() const { return age_; }
};
// Virtual inheritance to solve diamond problem
class Bird : public virtual Animal, public Flyable {
public:
Bird(const std::string& name, int age) : Animal(name, age) {
std::cout << "Bird constructor: " << name_ << std::endl;
}
~Bird() {
std::cout << "Bird destructor: " << name_ << std::endl;
}
void makeSound() const override {
std::cout << name_ << " chirps!" << std::endl;
}
double getMaxAltitude() const override {
return 10000.0; // 10km
}
};
class Fish : public virtual Animal, public Swimmable {
public:
Fish(const std::string& name, int age) : Animal(name, age) {
std::cout << "Fish constructor: " << name_ << std::endl;
}
~Fish() {
std::cout << "Fish destructor: " << name_ << std::endl;
}
void makeSound() const override {
std::cout << name_ << " blows bubbles!" << std::endl;
}
double getMaxDepth() const override {
return 1000.0; // 1km deep
}
};
// Multiple inheritance - can both fly and swim
class Duck : public Bird, public Swimmable {
public:
Duck(const std::string& name, int age) : Animal(name, age), Bird(name, age) {
std::cout << "Duck constructor: " << name_ << std::endl;
}
~Duck() {
std::cout << "Duck destructor: " << name_ << std::endl;
}
void makeSound() const override {
std::cout << name_ << " quacks!" << std::endl;
}
double getMaxDepth() const override {
return 5.0; // Shallow water
}
// Override flying behavior for ducks
void fly() const override {
std::cout << name_ << " flies low over the water!" << std::endl;
}
};
int main() {
std::cout << "=== Creating Duck ===" << std::endl;
Duck donald("Donald", 3);
std::cout << "\n=== Duck Abilities ===" << std::endl;
donald.makeSound();
donald.fly();
donald.swim();
std::cout << "Max altitude: " << donald.getMaxAltitude() << "m" << std::endl;
std::cout << "Max depth: " << donald.getMaxDepth() << "m" << std::endl;
std::cout << "\n=== Polymorphism ===" << std::endl;
Animal* animal = &donald;
Flyable* flyer = &donald;
Swimmable* swimmer = &donald;
animal->makeSound();
flyer->fly();
swimmer->swim();
std::cout << "\n=== Destruction ===" << std::endl;
return 0;
}

Operator Overloading

#include <iostream>
#include <iomanip>
#include <stdexcept>
class Complex {
private:
double real_, imag_;
public:
// Constructors
Complex(double real = 0.0, double imag = 0.0) : real_(real), imag_(imag) {}
// Copy constructor
Complex(const Complex& other) : real_(other.real_), imag_(other.imag_) {}
// Assignment operator
Complex& operator=(const Complex& other) {
if (this != &other) {
real_ = other.real_;
imag_ = other.imag_;
}
return *this;
}
// Arithmetic operators
Complex operator+(const Complex& other) const {
return Complex(real_ + other.real_, imag_ + other.imag_);
}
Complex operator-(const Complex& other) const {
return Complex(real_ - other.real_, imag_ - other.imag_);
}
Complex operator*(const Complex& other) const {
return Complex(
real_ * other.real_ - imag_ * other.imag_,
real_ * other.imag_ + imag_ * other.real_
);
}
Complex operator/(const Complex& other) const {
double denominator = other.real_ * other.real_ + other.imag_ * other.imag_;
if (denominator == 0.0) {
throw std::runtime_error("Division by zero complex number");
}
return Complex(
(real_ * other.real_ + imag_ * other.imag_) / denominator,
(imag_ * other.real_ - real_ * other.imag_) / denominator
);
}
// Compound assignment operators
Complex& operator+=(const Complex& other) {
real_ += other.real_;
imag_ += other.imag_;
return *this;
}
Complex& operator-=(const Complex& other) {
real_ -= other.real_;
imag_ -= other.imag_;
return *this;
}
// Unary operators
Complex operator-() const {
return Complex(-real_, -imag_);
}
Complex operator+() const {
return *this;
}
// Comparison operators
bool operator==(const Complex& other) const {
return (real_ == other.real_) && (imag_ == other.imag_);
}
bool operator!=(const Complex& other) const {
return !(*this == other);
}
// Increment/decrement operators
Complex& operator++() { // Prefix
++real_;
return *this;
}
Complex operator++(int) { // Postfix
Complex temp(*this);
++real_;
return temp;
}
// Subscript operator (access real/imaginary parts)
double& operator[](int index) {
if (index == 0) return real_;
if (index == 1) return imag_;
throw std::out_of_range("Complex number index out of range");
}
const double& operator[](int index) const {
if (index == 0) return real_;
if (index == 1) return imag_;
throw std::out_of_range("Complex number index out of range");
}
// Function call operator
double operator()() const {
return std::sqrt(real_ * real_ + imag_ * imag_); // Magnitude
}
// Type conversion operators
explicit operator double() const {
return (*this)(); // Convert to magnitude
}
// Stream operators (as friend functions)
friend std::ostream& operator<<(std::ostream& os, const Complex& c) {
os << std::fixed << std::setprecision(2);
if (c.imag_ >= 0) {
os << c.real_ << " + " << c.imag_ << "i";
} else {
os << c.real_ << " - " << (-c.imag_) << "i";
}
return os;
}
friend std::istream& operator>>(std::istream& is, Complex& c) {
std::cout << "Enter real part: ";
is >> c.real_;
std::cout << "Enter imaginary part: ";
is >> c.imag_;
return is;
}
// Accessors
double real() const { return real_; }
double imag() const { return imag_; }
};
int main() {
Complex c1(3.0, 4.0);
Complex c2(1.0, -2.0);
Complex c3;
std::cout << "c1 = " << c1 << std::endl;
std::cout << "c2 = " << c2 << std::endl;
// Arithmetic operations
std::cout << "\nArithmetic operations:" << std::endl;
std::cout << "c1 + c2 = " << (c1 + c2) << std::endl;
std::cout << "c1 - c2 = " << (c1 - c2) << std::endl;
std::cout << "c1 * c2 = " << (c1 * c2) << std::endl;
std::cout << "c1 / c2 = " << (c1 / c2) << std::endl;
// Compound assignment
c3 = c1;
c3 += c2;
std::cout << "\nc3 = c1; c3 += c2; c3 = " << c3 << std::endl;
// Unary operators
std::cout << "\nUnary operations:" << std::endl;
std::cout << "-c1 = " << (-c1) << std::endl;
std::cout << "+c1 = " << (+c1) << std::endl;
// Increment operators
Complex c4 = c1;
std::cout << "\nIncrement operations:" << std::endl;
std::cout << "c4 before ++: " << c4 << std::endl;
std::cout << "++c4: " << (++c4) << std::endl;
std::cout << "c4++: " << (c4++) << std::endl;
std::cout << "c4 after: " << c4 << std::endl;
// Subscript operator
std::cout << "\nSubscript access:" << std::endl;
std::cout << "c1[0] (real) = " << c1[0] << std::endl;
std::cout << "c1[1] (imag) = " << c1[1] << std::endl;
// Function call operator (magnitude)
std::cout << "\nMagnitude:" << std::endl;
std::cout << "|c1| = " << c1() << std::endl;
// Type conversion
std::cout << "\nType conversion:" << std::endl;
std::cout << "c1 as double: " << static_cast<double>(c1) << std::endl;
return 0;
}

Modern C++ Features

Lambda Expressions

Lambda expressions provide a concise way to create anonymous functions:

#include <iostream>
#include <vector>
#include <algorithm>
#include <functional>
int main() {
std::vector<int> numbers = {5, 2, 8, 1, 9, 3};
// Basic lambda
auto print = [](int x) { std::cout << x << " "; };
std::cout << "Original: ";
std::for_each(numbers.begin(), numbers.end(), print);
std::cout << std::endl;
// Lambda with capture by value
int threshold = 5;
auto countGreater = [threshold](const std::vector<int>& vec) {
return std::count_if(vec.begin(), vec.end(),
[threshold](int x) { return x > threshold; });
};
std::cout << "Numbers greater than " << threshold << ": "
<< countGreater(numbers) << std::endl;
// Lambda with capture by reference
int sum = 0;
std::for_each(numbers.begin(), numbers.end(),
[&sum](int x) { sum += x; });
std::cout << "Sum: " << sum << std::endl;
// Lambda with mutable capture
auto counter = [count = 0]() mutable { return ++count; };
std::cout << "Counter: " << counter() << ", " << counter()
<< ", " << counter() << std::endl;
// Generic lambda (C++14)
auto genericPrint = [](const auto& item) {
std::cout << "Item: " << item << std::endl;
};
genericPrint(42);
genericPrint(3.14);
genericPrint("Hello");
return 0;
}

Move Semantics and Perfect Forwarding

Move semantics eliminate unnecessary copying by transferring resources:

#include <iostream>
#include <vector>
#include <string>
#include <utility>
#include <numeric>
class MoveableResource {
private:
std::vector<int> data_;
std::string name_;
public:
// Constructor
MoveableResource(const std::string& name, size_t size)
: name_(name), data_(size) {
std::cout << "Constructing " << name_ << " with " << size << " elements" << std::endl;
std::iota(data_.begin(), data_.end(), 1);
}
// Copy constructor
MoveableResource(const MoveableResource& other)
: name_(other.name_ + "_copy"), data_(other.data_) {
std::cout << "Copy constructing " << name_ << std::endl;
}
// Move constructor
MoveableResource(MoveableResource&& other) noexcept
: name_(std::move(other.name_)), data_(std::move(other.data_)) {
std::cout << "Move constructing " << name_ << std::endl;
other.name_ = "moved_from";
}
// Copy assignment
MoveableResource& operator=(const MoveableResource& other) {
if (this != &other) {
name_ = other.name_ + "_assigned";
data_ = other.data_;
std::cout << "Copy assigning to " << name_ << std::endl;
}
return *this;
}
// Move assignment
MoveableResource& operator=(MoveableResource&& other) noexcept {
if (this != &other) {
name_ = std::move(other.name_);
data_ = std::move(other.data_);
std::cout << "Move assigning to " << name_ << std::endl;
other.name_ = "moved_from";
}
return *this;
}
// Destructor
~MoveableResource() {
std::cout << "Destructing " << name_ << std::endl;
}
const std::string& getName() const { return name_; }
size_t size() const { return data_.size(); }
};
// Perfect forwarding example
template<typename T>
void processResource(T&& resource) {
std::cout << "Processing: " << resource.getName() << std::endl;
}
template<typename... Args>
MoveableResource createResource(Args&&... args) {
return MoveableResource(std::forward<Args>(args)...);
}
int main() {
std::cout << "=== Creating resources ===" << std::endl;
MoveableResource res1("Resource1", 1000);
std::cout << "\n=== Copy vs Move ===" << std::endl;
MoveableResource res2 = res1; // Copy constructor
MoveableResource res3 = std::move(res1); // Move constructor
std::cout << "res1 name after move: " << res1.getName() << std::endl;
std::cout << "res3 name: " << res3.getName() << std::endl;
std::cout << "\n=== Assignment ===" << std::endl;
MoveableResource res4("Resource4", 500);
res4 = res2; // Copy assignment
res4 = std::move(res3); // Move assignment
std::cout << "\n=== Perfect forwarding ===" << std::endl;
auto res5 = createResource("Resource5", 750);
std::cout << "\n=== Function calls ===" << std::endl;
processResource(res2); // lvalue reference
processResource(MoveableResource("Temporary", 100)); // rvalue reference
std::cout << "\n=== End of scope ===" << std::endl;
return 0;
}

Range-Based Features

#include <iostream>
#include <vector>
#include <map>
#include <string>
#include <algorithm>
#include <ranges>
int main() {
// Range-based for loops
std::vector<int> numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
std::cout << "Original numbers: ";
for (const auto& num : numbers) {
std::cout << num << " ";
}
std::cout << std::endl;
// C++20 ranges and views
#if __cplusplus >= 202002L
// Filter even numbers and transform them
auto evenSquares = numbers
| std::views::filter([](int n) { return n % 2 == 0; })
| std::views::transform([](int n) { return n * n; });
std::cout << "Even squares: ";
for (const auto& num : evenSquares) {
std::cout << num << " ";
}
std::cout << std::endl;
// Take first 3 elements
auto firstThree = numbers | std::views::take(3);
std::cout << "First three: ";
for (const auto& num : firstThree) {
std::cout << num << " ";
}
std::cout << std::endl;
#endif
// Structured bindings with containers
std::map<std::string, int> scores = {
{"Alice", 95},
{"Bob", 87},
{"Charlie", 92}
};
std::cout << "\nScores:" << std::endl;
for (const auto& [name, score] : scores) {
std::cout << name << ": " << score << std::endl;
}
// Structured bindings with arrays
int arr[] = {1, 2, 3};
auto [a, b, c] = arr; // C++17 structured bindings
std::cout << "\nArray elements: " << a << ", " << b << ", " << c << std::endl;
return 0;
}

Exception Handling and RAII

Exception Safety Guarantees

C++ provides different levels of exception safety:

Exception Safety Levels:

  • No-throw guarantee: Operations never throw exceptions
  • Strong guarantee: Operations either succeed completely or leave program state unchanged
  • Basic guarantee: Operations don’t leak resources, but program state may change
  • No guarantee: Anything can happen
#include <iostream>
#include <vector>
#include <memory>
#include <stdexcept>
#include <fstream>
// RAII class for file handling
class FileHandler {
private:
std::fstream file_;
std::string filename_;
public:
FileHandler(const std::string& filename) : filename_(filename) {
file_.open(filename, std::ios::in | std::ios::out | std::ios::app);
if (!file_.is_open()) {
throw std::runtime_error("Failed to open file: " + filename);
}
std::cout << "File opened: " << filename_ << std::endl;
}
~FileHandler() {
if (file_.is_open()) {
file_.close();
std::cout << "File closed: " << filename_ << std::endl;
}
}
// Move constructor (no-throw guarantee)
FileHandler(FileHandler&& other) noexcept
: file_(std::move(other.file_)), filename_(std::move(other.filename_)) {
other.filename_.clear();
}
// Move assignment (no-throw guarantee)
FileHandler& operator=(FileHandler&& other) noexcept {
if (this != &other) {
if (file_.is_open()) {
file_.close();
}
file_ = std::move(other.file_);
filename_ = std::move(other.filename_);
other.filename_.clear();
}
return *this;
}
// Delete copy operations to prevent issues
FileHandler(const FileHandler&) = delete;
FileHandler& operator=(const FileHandler&) = delete;
void write(const std::string& data) {
if (!file_.is_open()) {
throw std::runtime_error("File not open for writing");
}
file_ << data << std::endl;
if (file_.fail()) {
throw std::runtime_error("Write operation failed");
}
}
std::string read() {
if (!file_.is_open()) {
throw std::runtime_error("File not open for reading");
}
std::string line;
if (std::getline(file_, line)) {
return line;
}
throw std::runtime_error("Read operation failed or EOF reached");
}
};
// Exception-safe vector operations
class SafeVector {
private:
std::vector<int> data_;
public:
// Strong exception safety guarantee
void safeAdd(int value) {
std::vector<int> temp = data_; // Copy current state
temp.push_back(value); // Modify copy
data_ = std::move(temp); // Commit changes (no-throw)
}
// Basic exception safety guarantee
void unsafeAdd(int value) {
data_.push_back(value); // May throw, but no resources leaked
// If this throws, vector is in valid but unspecified state
}
// No-throw guarantee
size_t size() const noexcept {
return data_.size();
}
// Strong exception safety for element access
int& at(size_t index) {
if (index >= data_.size()) {
throw std::out_of_range("Index out of range");
}
return data_[index]; // No-throw once bounds are checked
}
void print() const {
std::cout << "Vector contents: ";
for (const auto& item : data_) {
std::cout << item << " ";
}
std::cout << std::endl;
}
};
// Custom exception class
class CustomException : public std::exception {
private:
std::string message_;
int error_code_;
public:
CustomException(const std::string& message, int code)
: message_(message), error_code_(code) {}
const char* what() const noexcept override {
return message_.c_str();
}
int getErrorCode() const noexcept {
return error_code_;
}
};
// Function demonstrating exception handling
void demonstrateExceptions() {
try {
SafeVector vec;
// These operations are exception-safe
vec.safeAdd(1);
vec.safeAdd(2);
vec.safeAdd(3);
vec.print();
// This might throw
std::cout << "Element at index 1: " << vec.at(1) << std::endl;
std::cout << "Element at index 10: " << vec.at(10) << std::endl; // Will throw
} catch (const std::out_of_range& e) {
std::cout << "Out of range error: " << e.what() << std::endl;
} catch (const std::exception& e) {
std::cout << "Standard exception: " << e.what() << std::endl;
} catch (...) {
std::cout << "Unknown exception caught" << std::endl;
}
}
// RAII with multiple resources
class MultiResourceManager {
private:
std::unique_ptr<FileHandler> file1_, file2_;
std::unique_ptr<int[]> buffer_;
public:
MultiResourceManager(const std::string& file1, const std::string& file2, size_t bufferSize) {
try {
// Acquire resources in order
file1_ = std::make_unique<FileHandler>(file1);
file2_ = std::make_unique<FileHandler>(file2);
buffer_ = std::make_unique<int[]>(bufferSize);
std::cout << "All resources acquired successfully" << std::endl;
} catch (...) {
// RAII ensures partial cleanup happens automatically
std::cout << "Exception during resource acquisition" << std::endl;
throw; // Re-throw the exception
}
}
void processFiles() {
if (!file1_ || !file2_) {
throw std::runtime_error("Files not properly initialized");
}
try {
file1_->write("Processing data...");
file2_->write("Data processed successfully");
} catch (const std::exception& e) {
std::cout << "Error during file processing: " << e.what() << std::endl;
throw; // Re-throw for caller to handle
}
}
// Destructor automatically cleans up all resources
~MultiResourceManager() {
std::cout << "MultiResourceManager destroyed" << std::endl;
}
};
int main() {
std::cout << "=== Exception Safety Demo ===" << std::endl;
demonstrateExceptions();
std::cout << "\n=== RAII Demo ===" << std::endl;
try {
MultiResourceManager manager("test1.txt", "test2.txt", 1024);
manager.processFiles();
} catch (const CustomException& e) {
std::cout << "Custom exception: " << e.what()
<< " (Code: " << e.getErrorCode() << ")" << std::endl;
} catch (const std::exception& e) {
std::cout << "Standard exception: " << e.what() << std::endl;
} catch (...) {
std::cout << "Unknown exception occurred" << std::endl;
}
std::cout << "\n=== Stack Unwinding Demo ===" << std::endl;
// Demonstrate automatic cleanup during stack unwinding
try {
throw CustomException("Simulated error", 42);
} catch (const CustomException& e) {
std::cout << "Caught: " << e.what() << std::endl;
}
return 0;
}

Concurrency and Threading

Thread Basics and Synchronization

Modern C++ provides comprehensive threading support through the <thread> library:

#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <atomic>
#include <vector>
#include <queue>
#include <future>
#include <chrono>
#include <random>
// Thread-safe counter using mutex
class ThreadSafeCounter {
private:
mutable std::mutex mutex_;
int count_;
public:
ThreadSafeCounter(int initial = 0) : count_(initial) {}
void increment() {
std::lock_guard<std::mutex> lock(mutex_);
++count_;
}
void decrement() {
std::lock_guard<std::mutex> lock(mutex_);
--count_;
}
int getValue() const {
std::lock_guard<std::mutex> lock(mutex_);
return count_;
}
void add(int value) {
std::lock_guard<std::mutex> lock(mutex_);
count_ += value;
}
};
// Producer-Consumer pattern with condition variables
class ProducerConsumer {
private:
std::queue<int> buffer_;
std::mutex mutex_;
std::condition_variable condition_;
bool finished_;
size_t maxSize_;
public:
ProducerConsumer(size_t maxSize = 10) : finished_(false), maxSize_(maxSize) {}
void produce(int item) {
std::unique_lock<std::mutex> lock(mutex_);
// Wait until buffer has space
condition_.wait(lock, [this] { return buffer_.size() < maxSize_; });
buffer_.push(item);
std::cout << "Produced: " << item << " (buffer size: "
<< buffer_.size() << ")" << std::endl;
condition_.notify_all(); // Notify consumers
}
bool consume(int& item) {
std::unique_lock<std::mutex> lock(mutex_);
// Wait until buffer has items or production is finished
condition_.wait(lock, [this] { return !buffer_.empty() || finished_; });
if (buffer_.empty() && finished_) {
return false; // No more items to consume
}
item = buffer_.front();
buffer_.pop();
std::cout << "Consumed: " << item << " (buffer size: "
<< buffer_.size() << ")" << std::endl;
condition_.notify_all(); // Notify producers
return true;
}
void finish() {
std::lock_guard<std::mutex> lock(mutex_);
finished_ = true;
condition_.notify_all();
}
};
// Atomic operations example
class AtomicCounter {
private:
std::atomic<int> count_;
public:
AtomicCounter(int initial = 0) : count_(initial) {}
void increment() {
count_.fetch_add(1, std::memory_order_relaxed);
}
void decrement() {
count_.fetch_sub(1, std::memory_order_relaxed);
}
int getValue() const {
return count_.load(std::memory_order_relaxed);
}
// Compare and swap
bool compareAndSwap(int expected, int desired) {
return count_.compare_exchange_weak(expected, desired);
}
};
// Parallel computation example
class ParallelSum {
public:
static long long sequentialSum(const std::vector<int>& data) {
long long sum = 0;
for (int value : data) {
sum += value;
}
return sum;
}
static long long parallelSum(const std::vector<int>& data, size_t numThreads = 4) {
if (data.empty()) return 0;
std::vector<std::future<long long>> futures;
size_t chunkSize = data.size() / numThreads;
for (size_t i = 0; i < numThreads; ++i) {
size_t start = i * chunkSize;
size_t end = (i == numThreads - 1) ? data.size() : start + chunkSize;
futures.push_back(std::async(std::launch::async, [&data, start, end]() {
long long partialSum = 0;
for (size_t j = start; j < end; ++j) {
partialSum += data[j];
}
return partialSum;
}));
}
long long totalSum = 0;
for (auto& future : futures) {
totalSum += future.get();
}
return totalSum;
}
};
// Thread pool implementation
class ThreadPool {
private:
std::vector<std::thread> threads_;
std::queue<std::function<void()>> tasks_;
std::mutex mutex_;
std::condition_variable condition_;
bool stopped_;
public:
ThreadPool(size_t numThreads = std::thread::hardware_concurrency()) : stopped_(false) {
for (size_t i = 0; i < numThreads; ++i) {
threads_.emplace_back([this] {
while (true) {
std::function<void()> task;
{
std::unique_lock<std::mutex> lock(mutex_);
condition_.wait(lock, [this] { return stopped_ || !tasks_.empty(); });
if (stopped_ && tasks_.empty()) {
return;
}
task = std::move(tasks_.front());
tasks_.pop();
}
task();
}
});
}
}
template<typename F>
void enqueue(F&& task) {
{
std::lock_guard<std::mutex> lock(mutex_);
if (stopped_) {
throw std::runtime_error("ThreadPool is stopped");
}
tasks_.emplace(std::forward<F>(task));
}
condition_.notify_one();
}
~ThreadPool() {
{
std::lock_guard<std::mutex> lock(mutex_);
stopped_ = true;
}
condition_.notify_all();
for (auto& thread : threads_) {
if (thread.joinable()) {
thread.join();
}
}
}
};
void demonstrateBasicThreading() {
std::cout << "=== Basic Threading ===" << std::endl;
ThreadSafeCounter counter;
std::vector<std::thread> threads;
// Create multiple threads that increment the counter
for (int i = 0; i < 4; ++i) {
threads.emplace_back([&counter, i]() {
for (int j = 0; j < 10; ++j) {
counter.increment();
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
std::cout << "Thread " << i << " finished" << std::endl;
});
}
// Wait for all threads to complete
for (auto& thread : threads) {
thread.join();
}
std::cout << "Final counter value: " << counter.getValue() << std::endl;
}
void demonstrateProducerConsumer() {
std::cout << "\n=== Producer-Consumer ===" << std::endl;
ProducerConsumer pc(5); // Buffer size of 5
// Producer thread
std::thread producer([&pc]() {
for (int i = 1; i <= 20; ++i) {
pc.produce(i);
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
pc.finish();
std::cout << "Producer finished" << std::endl;
});
// Consumer threads
std::vector<std::thread> consumers;
for (int i = 0; i < 2; ++i) {
consumers.emplace_back([&pc, i]() {
int item;
while (pc.consume(item)) {
std::this_thread::sleep_for(std::chrono::milliseconds(150));
}
std::cout << "Consumer " << i << " finished" << std::endl;
});
}
producer.join();
for (auto& consumer : consumers) {
consumer.join();
}
}
void demonstrateAtomics() {
std::cout << "\n=== Atomic Operations ===" << std::endl;
AtomicCounter atomicCounter;
std::vector<std::thread> threads;
// Multiple threads incrementing atomically
for (int i = 0; i < 4; ++i) {
threads.emplace_back([&atomicCounter]() {
for (int j = 0; j < 1000; ++j) {
atomicCounter.increment();
}
});
}
for (auto& thread : threads) {
thread.join();
}
std::cout << "Atomic counter final value: " << atomicCounter.getValue() << std::endl;
}
void demonstrateParallelComputation() {
std::cout << "\n=== Parallel Computation ===" << std::endl;
// Create large vector of random numbers
std::vector<int> data(1000000);
std::random_device rd;
std::mt19937 gen(rd());
std::uniform_int_distribution<> dis(1, 100);
for (auto& value : data) {
value = dis(gen);
}
// Time sequential sum
auto start = std::chrono::high_resolution_clock::now();
long long seqSum = ParallelSum::sequentialSum(data);
auto seqTime = std::chrono::high_resolution_clock::now() - start;
// Time parallel sum
start = std::chrono::high_resolution_clock::now();
long long parSum = ParallelSum::parallelSum(data);
auto parTime = std::chrono::high_resolution_clock::now() - start;
std::cout << "Sequential sum: " << seqSum
<< " (time: " << std::chrono::duration_cast<std::chrono::microseconds>(seqTime).count()
<< " μs)" << std::endl;
std::cout << "Parallel sum: " << parSum
<< " (time: " << std::chrono::duration_cast<std::chrono::microseconds>(parTime).count()
<< " μs)" << std::endl;
std::cout << "Speedup: " << (double)seqTime.count() / parTime.count() << "x" << std::endl;
}
void demonstrateThreadPool() {
std::cout << "\n=== Thread Pool ===" << std::endl;
ThreadPool pool(4);
std::vector<std::future<int>> results;
// Submit tasks to thread pool
for (int i = 0; i < 10; ++i) {
auto future = std::async(std::launch::deferred, [i]() -> int {
std::this_thread::sleep_for(std::chrono::milliseconds(100));
return i * i;
});
pool.enqueue([future = std::move(future)]() mutable {
int result = future.get();
std::cout << "Task completed with result: " << result << std::endl;
});
}
// Let tasks complete
std::this_thread::sleep_for(std::chrono::seconds(2));
}
int main() {
std::cout << "Hardware concurrency: " << std::thread::hardware_concurrency() << std::endl;
demonstrateBasicThreading();
demonstrateProducerConsumer();
demonstrateAtomics();
demonstrateParallelComputation();
demonstrateThreadPool();
return 0;
}

Best Practices and Design Patterns

SOLID Principles in C++

The SOLID principles guide object-oriented design:

#include <iostream>
#include <memory>
#include <vector>
#include <string>
#include <fstream>
// S - Single Responsibility Principle
// Each class should have only one reason to change
class Logger {
public:
virtual ~Logger() = default;
virtual void log(const std::string& message) = 0;
};
class FileLogger : public Logger {
private:
std::string filename_;
public:
FileLogger(const std::string& filename) : filename_(filename) {}
void log(const std::string& message) override {
std::ofstream file(filename_, std::ios::app);
file << message << std::endl;
}
};
class ConsoleLogger : public Logger {
public:
void log(const std::string& message) override {
std::cout << "[LOG] " << message << std::endl;
}
};
// O - Open/Closed Principle
// Classes should be open for extension, closed for modification
class Shape {
public:
virtual ~Shape() = default;
virtual double area() const = 0;
virtual void draw() const = 0;
};
class Rectangle : public Shape {
private:
double width_, height_;
public:
Rectangle(double w, double h) : width_(w), height_(h) {}
double area() const override {
return width_ * height_;
}
void draw() const override {
std::cout << "Drawing rectangle " << width_ << "x" << height_ << std::endl;
}
};
class Circle : public Shape {
private:
double radius_;
public:
Circle(double r) : radius_(r) {}
double area() const override {
return 3.14159 * radius_ * radius_;
}
void draw() const override {
std::cout << "Drawing circle with radius " << radius_ << std::endl;
}
};
// We can add new shapes without modifying existing code
class Triangle : public Shape {
private:
double base_, height_;
public:
Triangle(double b, double h) : base_(b), height_(h) {}
double area() const override {
return 0.5 * base_ * height_;
}
void draw() const override {
std::cout << "Drawing triangle " << base_ << "x" << height_ << std::endl;
}
};
// L - Liskov Substitution Principle
// Derived classes must be substitutable for their base classes
class Bird {
public:
virtual ~Bird() = default;
virtual void makeSound() const = 0;
virtual void move() const = 0;
};
class FlyingBird : public Bird {
public:
virtual void fly() const = 0;
void move() const override {
fly();
}
};
class WalkingBird : public Bird {
public:
virtual void walk() const = 0;
void move() const override {
walk();
}
};
class Eagle : public FlyingBird {
public:
void makeSound() const override {
std::cout << "Eagle screeches" << std::endl;
}
void fly() const override {
std::cout << "Eagle soars high" << std::endl;
}
};
class Penguin : public WalkingBird {
public:
void makeSound() const override {
std::cout << "Penguin squawks" << std::endl;
}
void walk() const override {
std::cout << "Penguin waddles" << std::endl;
}
};
// I - Interface Segregation Principle
// Clients should not depend on interfaces they don't use
class Workable {
public:
virtual ~Workable() = default;
virtual void work() = 0;
};
class Eatable {
public:
virtual ~Eatable() = default;
virtual void eat() = 0;
};
class Sleepable {
public:
virtual ~Sleepable() = default;
virtual void sleep() = 0;
};
class Human : public Workable, public Eatable, public Sleepable {
public:
void work() override {
std::cout << "Human working" << std::endl;
}
void eat() override {
std::cout << "Human eating" << std::endl;
}
void sleep() override {
std::cout << "Human sleeping" << std::endl;
}
};
class Robot : public Workable { // Robot doesn't need to eat or sleep
public:
void work() override {
std::cout << "Robot working" << std::endl;
}
};
// D - Dependency Inversion Principle
// High-level modules should not depend on low-level modules
class Database {
public:
virtual ~Database() = default;
virtual void save(const std::string& data) = 0;
virtual std::string load(const std::string& key) = 0;
};
class MySQLDatabase : public Database {
public:
void save(const std::string& data) override {
std::cout << "Saving to MySQL: " << data << std::endl;
}
std::string load(const std::string& key) override {
return "Data from MySQL for key: " + key;
}
};
class MongoDatabase : public Database {
public:
void save(const std::string& data) override {
std::cout << "Saving to MongoDB: " << data << std::endl;
}
std::string load(const std::string& key) override {
return "Data from MongoDB for key: " + key;
}
};
class UserService {
private:
std::unique_ptr<Database> database_;
std::unique_ptr<Logger> logger_;
public:
UserService(std::unique_ptr<Database> db, std::unique_ptr<Logger> log)
: database_(std::move(db)), logger_(std::move(log)) {}
void saveUser(const std::string& userData) {
logger_->log("Saving user: " + userData);
database_->save(userData);
}
std::string getUser(const std::string& userId) {
logger_->log("Loading user: " + userId);
return database_->load(userId);
}
};

Common Design Patterns

#include <iostream>
#include <memory>
#include <vector>
#include <string>
#include <map>
#include <functional>
// Singleton Pattern
class DatabaseConnection {
private:
static std::unique_ptr<DatabaseConnection> instance_;
static std::mutex mutex_;
DatabaseConnection() {
std::cout << "Database connection established" << std::endl;
}
public:
static DatabaseConnection& getInstance() {
std::lock_guard<std::mutex> lock(mutex_);
if (!instance_) {
instance_ = std::unique_ptr<DatabaseConnection>(new DatabaseConnection());
}
return *instance_;
}
void query(const std::string& sql) {
std::cout << "Executing query: " << sql << std::endl;
}
// Delete copy constructor and assignment operator
DatabaseConnection(const DatabaseConnection&) = delete;
DatabaseConnection& operator=(const DatabaseConnection&) = delete;
};
std::unique_ptr<DatabaseConnection> DatabaseConnection::instance_ = nullptr;
std::mutex DatabaseConnection::mutex_;
// Factory Pattern
class Animal {
public:
virtual ~Animal() = default;
virtual void makeSound() const = 0;
virtual std::string getType() const = 0;
};
class Dog : public Animal {
public:
void makeSound() const override {
std::cout << "Woof!" << std::endl;
}
std::string getType() const override {
return "Dog";
}
};
class Cat : public Animal {
public:
void makeSound() const override {
std::cout << "Meow!" << std::endl;
}
std::string getType() const override {
return "Cat";
}
};
class AnimalFactory {
public:
static std::unique_ptr<Animal> createAnimal(const std::string& type) {
if (type == "dog") {
return std::make_unique<Dog>();
} else if (type == "cat") {
return std::make_unique<Cat>();
}
return nullptr;
}
// Register-based factory for extensibility
using CreatorFunction = std::function<std::unique_ptr<Animal>()>;
static std::map<std::string, CreatorFunction> creators_;
static void registerCreator(const std::string& type, CreatorFunction creator) {
creators_[type] = creator;
}
static std::unique_ptr<Animal> create(const std::string& type) {
auto it = creators_.find(type);
if (it != creators_.end()) {
return it->second();
}
return nullptr;
}
};
std::map<std::string, AnimalFactory::CreatorFunction> AnimalFactory::creators_;
// Observer Pattern
class Observer {
public:
virtual ~Observer() = default;
virtual void update(const std::string& message) = 0;
};
class Subject {
private:
std::vector<Observer*> observers_;
public:
void addObserver(Observer* observer) {
observers_.push_back(observer);
}
void removeObserver(Observer* observer) {
observers_.erase(
std::remove(observers_.begin(), observers_.end(), observer),
observers_.end()
);
}
void notifyObservers(const std::string& message) {
for (auto* observer : observers_) {
observer->update(message);
}
}
};
class NewsAgency : public Subject {
private:
std::string news_;
public:
void setNews(const std::string& news) {
news_ = news;
notifyObservers(news_);
}
std::string getNews() const {
return news_;
}
};
class NewsChannel : public Observer {
private:
std::string name_;
public:
NewsChannel(const std::string& name) : name_(name) {}
void update(const std::string& message) override {
std::cout << name_ << " received news: " << message << std::endl;
}
};
// Strategy Pattern
class SortStrategy {
public:
virtual ~SortStrategy() = default;
virtual void sort(std::vector<int>& data) = 0;
virtual std::string getName() const = 0;
};
class BubbleSort : public SortStrategy {
public:
void sort(std::vector<int>& data) override {
for (size_t i = 0; i < data.size(); ++i) {
for (size_t j = 0; j < data.size() - 1 - i; ++j) {
if (data[j] > data[j + 1]) {
std::swap(data[j], data[j + 1]);
}
}
}
}
std::string getName() const override {
return "Bubble Sort";
}
};
class QuickSort : public SortStrategy {
private:
void quickSort(std::vector<int>& data, int low, int high) {
if (low < high) {
int pivot = partition(data, low, high);
quickSort(data, low, pivot - 1);
quickSort(data, pivot + 1, high);
}
}
int partition(std::vector<int>& data, int low, int high) {
int pivot = data[high];
int i = low - 1;
for (int j = low; j < high; ++j) {
if (data[j] < pivot) {
++i;
std::swap(data[i], data[j]);
}
}
std::swap(data[i + 1], data[high]);
return i + 1;
}
public:
void sort(std::vector<int>& data) override {
if (!data.empty()) {
quickSort(data, 0, data.size() - 1);
}
}
std::string getName() const override {
return "Quick Sort";
}
};
class SortContext {
private:
std::unique_ptr<SortStrategy> strategy_;
public:
void setStrategy(std::unique_ptr<SortStrategy> strategy) {
strategy_ = std::move(strategy);
}
void sort(std::vector<int>& data) {
if (strategy_) {
std::cout << "Using " << strategy_->getName() << std::endl;
strategy_->sort(data);
}
}
};
// Command Pattern
class Command {
public:
virtual ~Command() = default;
virtual void execute() = 0;
virtual void undo() = 0;
};
class Light {
private:
bool isOn_;
public:
Light() : isOn_(false) {}
void turnOn() {
isOn_ = true;
std::cout << "Light is ON" << std::endl;
}
void turnOff() {
isOn_ = false;
std::cout << "Light is OFF" << std::endl;
}
bool getState() const { return isOn_; }
};
class LightOnCommand : public Command {
private:
Light& light_;
public:
LightOnCommand(Light& light) : light_(light) {}
void execute() override {
light_.turnOn();
}
void undo() override {
light_.turnOff();
}
};
class LightOffCommand : public Command {
private:
Light& light_;
public:
LightOffCommand(Light& light) : light_(light) {}
void execute() override {
light_.turnOff();
}
void undo() override {
light_.turnOn();
}
};
class RemoteControl {
private:
std::vector<std::unique_ptr<Command>> history_;
public:
void executeCommand(std::unique_ptr<Command> command) {
command->execute();
history_.push_back(std::move(command));
}
void undo() {
if (!history_.empty()) {
history_.back()->undo();
history_.pop_back();
}
}
};
int main() {
std::cout << "=== SOLID Principles Demo ===" << std::endl;
// Dependency Inversion
auto database = std::make_unique<MySQLDatabase>();
auto logger = std::make_unique<ConsoleLogger>();
UserService service(std::move(database), std::move(logger));
service.saveUser("John Doe");
std::cout << service.getUser("123") << std::endl;
std::cout << "\n=== Design Patterns Demo ===" << std::endl;
// Singleton
DatabaseConnection::getInstance().query("SELECT * FROM users");
// Factory
AnimalFactory::registerCreator("dog", []() { return std::make_unique<Dog>(); });
AnimalFactory::registerCreator("cat", []() { return std::make_unique<Cat>(); });
auto dog = AnimalFactory::create("dog");
auto cat = AnimalFactory::create("cat");
if (dog) dog->makeSound();
if (cat) cat->makeSound();
// Observer
NewsAgency agency;
NewsChannel cnn("CNN");
NewsChannel bbc("BBC");
agency.addObserver(&cnn);
agency.addObserver(&bbc);
agency.setNews("Breaking: Design patterns are awesome!");
// Strategy
SortContext sorter;
std::vector<int> data1 = {64, 34, 25, 12, 22, 11, 90};
std::vector<int> data2 = data1;
sorter.setStrategy(std::make_unique<BubbleSort>());
sorter.sort(data1);
sorter.setStrategy(std::make_unique<QuickSort>());
sorter.sort(data2);
// Command
Light light;
RemoteControl remote;
remote.executeCommand(std::make_unique<LightOnCommand>(light));
remote.executeCommand(std::make_unique<LightOffCommand>(light));
remote.undo(); // Turn light back on
return 0;
}

Performance Optimization

Memory Optimization and Cache-Friendly Code

#include <iostream>
#include <vector>
#include <chrono>
#include <algorithm>
#include <memory>
#include <random>
// Cache-friendly data structures
struct Point3D_AoS { // Array of Structures
float x, y, z;
Point3D_AoS(float x = 0, float y = 0, float z = 0) : x(x), y(y), z(z) {}
};
class Points3D_SoA { // Structure of Arrays - more cache friendly
private:
std::vector<float> x_, y_, z_;
public:
void reserve(size_t size) {
x_.reserve(size);
y_.reserve(size);
z_.reserve(size);
}
void addPoint(float x, float y, float z) {
x_.push_back(x);
y_.push_back(y);
z_.push_back(z);
}
size_t size() const { return x_.size(); }
float getX(size_t i) const { return x_[i]; }
float getY(size_t i) const { return y_[i]; }
float getZ(size_t i) const { return z_[i]; }
// Process all X coordinates (cache-friendly)
void processX(std::function<void(float&)> func) {
for (auto& x : x_) {
func(x);
}
}
};
// Memory pool allocator for frequent allocations
template<typename T, size_t BlockSize = 1024>
class MemoryPool {
private:
struct Block {
alignas(T) char data[sizeof(T)];
Block* next;
};
Block* freeBlocks_;
std::vector<std::unique_ptr<Block[]>> pools_;
size_t blockIndex_;
public:
MemoryPool() : freeBlocks_(nullptr), blockIndex_(0) {
allocateNewPool();
}
~MemoryPool() {
// Destructor automatically cleans up pools
}
T* allocate() {
if (!freeBlocks_) {
allocateNewPool();
}
Block* block = freeBlocks_;
freeBlocks_ = freeBlocks_->next;
return reinterpret_cast<T*>(block);
}
void deallocate(T* ptr) {
Block* block = reinterpret_cast<Block*>(ptr);
block->next = freeBlocks_;
freeBlocks_ = block;
}
private:
void allocateNewPool() {
auto newPool = std::make_unique<Block[]>(BlockSize);
// Link all blocks in the new pool
for (size_t i = 0; i < BlockSize - 1; ++i) {
newPool[i].next = &newPool[i + 1];
}
newPool[BlockSize - 1].next = freeBlocks_;
freeBlocks_ = &newPool[0];
pools_.push_back(std::move(newPool));
}
};
// Benchmark class for performance testing
class Benchmark {
public:
template<typename Func>
static double measureTime(Func&& func, int iterations = 1) {
auto start = std::chrono::high_resolution_clock::now();
for (int i = 0; i < iterations; ++i) {
func();
}
auto end = std::chrono::high_resolution_clock::now();
auto duration = std::chrono::duration_cast<std::chrono::microseconds>(end - start);
return duration.count() / 1000.0; // Return milliseconds
}
static void compareDataStructures() {
constexpr size_t NUM_POINTS = 1000000;
std::cout << "=== Data Structure Comparison ===" << std::endl;
// Array of Structures
std::vector<Point3D_AoS> aos_points;
aos_points.reserve(NUM_POINTS);
// Structure of Arrays
Points3D_SoA soa_points;
soa_points.reserve(NUM_POINTS);
// Fill data
std::random_device rd;
std::mt19937 gen(rd());
std::uniform_real_distribution<float> dis(0.0f, 100.0f);
for (size_t i = 0; i < NUM_POINTS; ++i) {
float x = dis(gen), y = dis(gen), z = dis(gen);
aos_points.emplace_back(x, y, z);
soa_points.addPoint(x, y, z);
}
// Benchmark: Process only X coordinates
double aos_time = measureTime([&aos_points]() {
float sum = 0.0f;
for (const auto& point : aos_points) {
sum += point.x; // Cache miss for y, z data
}
return sum;
}, 100);
double soa_time = measureTime([&soa_points]() {
float sum = 0.0f;
for (size_t i = 0; i < soa_points.size(); ++i) {
sum += soa_points.getX(i); // Only X data in cache
}
return sum;
}, 100);
std::cout << "AoS time: " << aos_time << " ms" << std::endl;
std::cout << "SoA time: " << soa_time << " ms" << std::endl;
std::cout << "SoA speedup: " << aos_time / soa_time << "x" << std::endl;
}
static void compareMemoryAllocators() {
constexpr int NUM_ALLOCATIONS = 100000;
std::cout << "\n=== Memory Allocator Comparison ===" << std::endl;
// Standard allocator
double standard_time = measureTime([]() {
std::vector<int*> ptrs;
ptrs.reserve(NUM_ALLOCATIONS);
for (int i = 0; i < NUM_ALLOCATIONS; ++i) {
ptrs.push_back(new int(i));
}
for (auto* ptr : ptrs) {
delete ptr;
}
});
// Memory pool allocator
double pool_time = measureTime([]() {
MemoryPool<int> pool;
std::vector<int*> ptrs;
ptrs.reserve(NUM_ALLOCATIONS);
for (int i = 0; i < NUM_ALLOCATIONS; ++i) {
int* ptr = pool.allocate();
*ptr = i;
ptrs.push_back(ptr);
}
for (auto* ptr : ptrs) {
pool.deallocate(ptr);
}
});
std::cout << "Standard allocator time: " << standard_time << " ms" << std::endl;
std::cout << "Memory pool time: " << pool_time << " ms" << std::endl;
std::cout << "Pool speedup: " << standard_time / pool_time << "x" << std::endl;
}
};
// SIMD optimization example (conceptual)
class SIMDOperations {
public:
// Scalar version
static void addArraysScalar(const float* a, const float* b, float* result, size_t size) {
for (size_t i = 0; i < size; ++i) {
result[i] = a[i] + b[i];
}
}
// Vectorized version (would use actual SIMD intrinsics in real code)
static void addArraysVectorized(const float* a, const float* b, float* result, size_t size) {
// This is a conceptual example - real SIMD would use intrinsics
constexpr size_t VECTOR_SIZE = 4; // Process 4 floats at once
size_t vectorized_size = (size / VECTOR_SIZE) * VECTOR_SIZE;
// Process in chunks of 4
for (size_t i = 0; i < vectorized_size; i += VECTOR_SIZE) {
for (size_t j = 0; j < VECTOR_SIZE; ++j) {
result[i + j] = a[i + j] + b[i + j];
}
}
// Handle remaining elements
for (size_t i = vectorized_size; i < size; ++i) {
result[i] = a[i] + b[i];
}
}
static void benchmarkSIMD() {
constexpr size_t ARRAY_SIZE = 1000000;
std::vector<float> a(ARRAY_SIZE), b(ARRAY_SIZE), result1(ARRAY_SIZE), result2(ARRAY_SIZE);
// Initialize arrays
std::random_device rd;
std::mt19937 gen(rd());
std::uniform_real_distribution<float> dis(0.0f, 100.0f);
for (size_t i = 0; i < ARRAY_SIZE; ++i) {
a[i] = dis(gen);
b[i] = dis(gen);
}
std::cout << "\n=== SIMD Comparison ===" << std::endl;
double scalar_time = Benchmark::measureTime([&]() {
addArraysScalar(a.data(), b.data(), result1.data(), ARRAY_SIZE);
}, 1000);
double vectorized_time = Benchmark::measureTime([&]() {
addArraysVectorized(a.data(), b.data(), result2.data(), ARRAY_SIZE);
}, 1000);
std::cout << "Scalar time: " << scalar_time << " ms" << std::endl;
std::cout << "Vectorized time: " << vectorized_time << " ms" << std::endl;
std::cout << "Vectorized speedup: " << scalar_time / vectorized_time << "x" << std::endl;
}
};
// Branch prediction optimization
class BranchOptimization {
public:
// Poor branch prediction - unpredictable pattern
static int countPositiveUnpredictable(const std::vector<int>& data) {
int count = 0;
for (int value : data) {
if (value > 0) { // Unpredictable branch
++count;
}
}
return count;
}
// Better branch prediction - data sorted
static int countPositivePredictable(std::vector<int> data) {
std::sort(data.begin(), data.end()); // Sort for predictable branches
int count = 0;
for (int value : data) {
if (value > 0) {
++count;
}
}
return count;
}
// Branchless version using arithmetic
static int countPositiveBranchless(const std::vector<int>& data) {
int count = 0;
for (int value : data) {
count += (value > 0) ? 1 : 0; // Still has branch, but simpler
}
return count;
}
static void benchmarkBranching() {
constexpr size_t DATA_SIZE = 1000000;
std::vector<int> data(DATA_SIZE);
// Create random data with mixed positive/negative values
std::random_device rd;
std::mt19937 gen(rd());
std::uniform_int_distribution<int> dis(-100, 100);
for (auto& value : data) {
value = dis(gen);
}
std::cout << "\n=== Branch Prediction Comparison ===" << std::endl;
double unpredictable_time = Benchmark::measureTime([&]() {
return countPositiveUnpredictable(data);
}, 100);
double predictable_time = Benchmark::measureTime([&]() {
return countPositivePredictable(data);
}, 100);
double branchless_time = Benchmark::measureTime([&]() {
return countPositiveBranchless(data);
}, 100);
std::cout << "Unpredictable branches: " << unpredictable_time << " ms" << std::endl;
std::cout << "Predictable branches: " << predictable_time << " ms" << std::endl;
std::cout << "Branchless: " << branchless_time << " ms" << std::endl;
}
};
int main() {
std::cout << "=== Performance Optimization Examples ===" << std::endl;
Benchmark::compareDataStructures();
Benchmark::compareMemoryAllocators();
SIMDOperations::benchmarkSIMD();
BranchOptimization::benchmarkBranching();
return 0;
}

Advanced Topics and Modern Features

Coroutines (C++20)

#include <iostream>
#include <coroutine>
#include <vector>
#include <optional>
#include <thread>
#include <chrono>
// Simple generator coroutine
template<typename T>
class Generator {
public:
struct promise_type {
T current_value;
Generator get_return_object() {
return Generator{std::coroutine_handle<promise_type>::from_promise(*this)};
}
std::suspend_always initial_suspend() { return {}; }
std::suspend_always final_suspend() noexcept { return {}; }
void unhandled_exception() { std::terminate(); }
std::suspend_always yield_value(T value) {
current_value = value;
return {};
}
void return_void() {}
};
private:
std::coroutine_handle<promise_type> handle;
public:
explicit Generator(std::coroutine_handle<promise_type> h) : handle(h) {}
~Generator() {
if (handle) {
handle.destroy();
}
}
// Move-only type
Generator(const Generator&) = delete;
Generator& operator=(const Generator&) = delete;
Generator(Generator&& other) noexcept : handle(other.handle) {
other.handle = {};
}
Generator& operator=(Generator&& other) noexcept {
if (this != &other) {
if (handle) {
handle.destroy();
}
handle = other.handle;
other.handle = {};
}
return *this;
}
bool next() {
if (handle) {
handle.resume();
return !handle.done();
}
return false;
}
T getValue() const {
return handle.promise().current_value;
}
};
// Fibonacci generator using coroutines
Generator<int> fibonacci() {
int a = 0, b = 1;
while (true) {
co_yield a;
auto temp = a;
a = b;
b = temp + b;
}
}
// Range generator
Generator<int> range(int start, int end, int step = 1) {
for (int i = start; i < end; i += step) {
co_yield i;
}
}
// Async task coroutine
template<typename T>
class Task {
public:
struct promise_type {
T value;
std::exception_ptr exception;
Task get_return_object() {
return Task{std::coroutine_handle<promise_type>::from_promise(*this)};
}
std::suspend_never initial_suspend() { return {}; }
std::suspend_always final_suspend() noexcept { return {}; }
void unhandled_exception() {
exception = std::current_exception();
}
void return_value(T val) {
value = val;
}
};
private:
std::coroutine_handle<promise_type> handle;
public:
explicit Task(std::coroutine_handle<promise_type> h) : handle(h) {}
~Task() {
if (handle) {
handle.destroy();
}
}
Task(const Task&) = delete;
Task& operator=(const Task&) = delete;
Task(Task&& other) noexcept : handle(other.handle) {
other.handle = {};
}
Task& operator=(Task&& other) noexcept {
if (this != &other) {
if (handle) {
handle.destroy();
}
handle = other.handle;
other.handle = {};
}
return *this;
}
T get() {
if (!handle.done()) {
handle.resume();
}
if (handle.promise().exception) {
std::rethrow_exception(handle.promise().exception);
}
return handle.promise().value;
}
bool is_ready() const {
return handle.done();
}
};
// Simple awaitable type
struct SleepAwaitable {
std::chrono::milliseconds duration;
bool await_ready() const noexcept { return false; }
void await_suspend(std::coroutine_handle<> handle) {
std::thread([handle, duration = this->duration]() {
std::this_thread::sleep_for(duration);
handle.resume();
}).detach();
}
void await_resume() noexcept {}
};
SleepAwaitable sleep_for(std::chrono::milliseconds ms) {
return SleepAwaitable{ms};
}
// Async computation example
Task<int> computeAsync(int x) {
std::cout << "Starting async computation for " << x << std::endl;
co_await sleep_for(std::chrono::milliseconds(100));
int result = x * x;
std::cout << "Computed " << x << "^2 = " << result << std::endl;
co_return result;
}
void demonstrateCoroutines() {
std::cout << "=== Coroutines Demo ===" << std::endl;
// Generator example
std::cout << "\nFibonacci sequence (first 10):" << std::endl;
auto fib = fibonacci();
for (int i = 0; i < 10 && fib.next(); ++i) {
std::cout << fib.getValue() << " ";
}
std::cout << std::endl;
// Range generator
std::cout << "\nRange 0 to 20 step 3:" << std::endl;
auto r = range(0, 20, 3);
while (r.next()) {
std::cout << r.getValue() << " ";
}
std::cout << std::endl;
// Async task
std::cout << "\nAsync computation:" << std::endl;
auto task = computeAsync(5);
std::cout << "Task started, doing other work..." << std::endl;
std::this_thread::sleep_for(std::chrono::milliseconds(50));
std::cout << "Getting result..." << std::endl;
int result = task.get();
std::cout << "Final result: " << result << std::endl;
}

Modules (C++20 Preview)

// Note: Module support varies by compiler and is still evolving
// This is a conceptual example of what C++20 modules look like
/*
// math_module.ixx (module interface)
export module math_utils;
import <iostream>;
export namespace math_utils {
int add(int a, int b);
int multiply(int a, int b);
template<typename T>
T power(T base, int exponent) {
T result = 1;
for (int i = 0; i < exponent; ++i) {
result *= base;
}
return result;
}
class Calculator {
public:
void set_precision(int precision);
double calculate(const std::string& expression);
private:
int precision_ = 6;
};
}
// math_module.cpp (module implementation)
module math_utils;
int math_utils::add(int a, int b) {
return a + b;
}
int math_utils::multiply(int a, int b) {
return a * b;
}
void math_utils::Calculator::set_precision(int precision) {
precision_ = precision;
}
double math_utils::Calculator::calculate(const std::string& expression) {
// Simplified calculator implementation
return 42.0;
}
*/
// Using modules would look like this:
/*
import math_utils;
import <iostream>;
int main() {
std::cout << math_utils::add(5, 3) << std::endl;
std::cout << math_utils::power(2.0, 10) << std::endl;
math_utils::Calculator calc;
calc.set_precision(2);
std::cout << calc.calculate("2 + 2") << std::endl;
return 0;
}
*/
void demonstrateModulesConceptually() {
std::cout << "\n=== Modules (Conceptual) ===" << std::endl;
std::cout << "Modules provide better encapsulation and faster compilation" << std::endl;
std::cout << "- No more header/implementation split for templates" << std::endl;
std::cout << "- Better compile times" << std::endl;
std::cout << "- Isolated macro scope" << std::endl;
std::cout << "- Explicit export of public interface" << std::endl;
}

Ranges and Views (C++20)

#include <iostream>
#include <vector>
#include <string>
#include <algorithm>
#include <numeric>
#if __cplusplus >= 202002L
#include <ranges>
void demonstrateRanges() {
std::cout << "\n=== Ranges and Views (C++20) ===" << std::endl;
std::vector<int> numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
// Traditional approach
std::vector<int> traditional_result;
std::copy_if(numbers.begin(), numbers.end(),
std::back_inserter(traditional_result),
[](int n) { return n % 2 == 0; });
std::transform(traditional_result.begin(), traditional_result.end(),
traditional_result.begin(),
[](int n) { return n * n; });
std::cout << "Traditional approach: ";
for (int n : traditional_result) {
std::cout << n << " ";
}
std::cout << std::endl;
// Ranges approach - more readable and efficient
auto even_squares = numbers
| std::views::filter([](int n) { return n % 2 == 0; })
| std::views::transform([](int n) { return n * n; });
std::cout << "Ranges approach: ";
for (int n : even_squares) {
std::cout << n << " ";
}
std::cout << std::endl;
// More complex pipeline
std::vector<std::string> words = {"hello", "world", "cpp", "ranges", "are", "awesome"};
auto long_upper_words = words
| std::views::filter([](const std::string& s) { return s.length() > 3; })
| std::views::transform([](const std::string& s) {
std::string upper = s;
std::transform(upper.begin(), upper.end(), upper.begin(), ::toupper);
return upper;
})
| std::views::take(3);
std::cout << "Long uppercase words (first 3): ";
for (const auto& word : long_upper_words) {
std::cout << word << " ";
}
std::cout << std::endl;
// Generate ranges
auto infinite_ints = std::views::iota(1); // 1, 2, 3, 4, ...
auto first_ten = infinite_ints | std::views::take(10);
std::cout << "First 10 integers: ";
for (int n : first_ten) {
std::cout << n << " ";
}
std::cout << std::endl;
// Zip view (if available)
std::vector<int> ids = {1, 2, 3, 4};
std::vector<std::string> names = {"Alice", "Bob", "Charlie", "Diana"};
// Note: zip might not be available in all C++20 implementations yet
std::cout << "ID-Name pairs:" << std::endl;
for (size_t i = 0; i < std::min(ids.size(), names.size()); ++i) {
std::cout << ids[i] << ": " << names[i] << std::endl;
}
}
#else
void demonstrateRanges() {
std::cout << "\n=== Ranges (C++20 not available) ===" << std::endl;
std::cout << "Ranges provide a more functional approach to data processing" << std::endl;
std::cout << "- Composable views" << std::endl;
std::cout << "- Lazy evaluation" << std::endl;
std::cout << "- Better readability" << std::endl;
std::cout << "- No intermediate containers" << std::endl;
}
#endif

Complete Application Example

Here’s a comprehensive example that demonstrates many of the concepts covered:

#include <iostream>
#include <memory>
#include <vector>
#include <algorithm>
#include <string>
#include <map>
#include <functional>
#include <thread>
#include <mutex>
#include <future>
#include <atomic>
#include <iomanip>
#include <sstream>
// Modern C++ application: Task Management System
class Task {
private:
static std::atomic<int> next_id_;
int id_;
std::string description_;
bool completed_;
public:
Task(std::string description)
: id_(next_id_++), description_(std::move(description)), completed_(false) {}
int getId() const { return id_; }
const std::string& getDescription() const { return description_; }
bool isCompleted() const { return completed_; }
void complete() { completed_ = true; }
void setDescription(const std::string& desc) { description_ = desc; }
friend std::ostream& operator<<(std::ostream& os, const Task& task) {
os << "[" << task.id_ << "] " << task.description_
<< " (" << (task.completed_ ? "" : "") << ")";
return os;
}
};
std::atomic<int> Task::next_id_{1};
// Thread-safe task manager
class TaskManager {
private:
mutable std::mutex mutex_;
std::vector<std::unique_ptr<Task>> tasks_;
public:
void addTask(const std::string& description) {
std::lock_guard<std::mutex> lock(mutex_);
tasks_.push_back(std::make_unique<Task>(description));
std::cout << "Added task: " << description << std::endl;
}
bool completeTask(int id) {
std::lock_guard<std::mutex> lock(mutex_);
auto it = std::find_if(tasks_.begin(), tasks_.end(),
[id](const std::unique_ptr<Task>& task) {
return task->getId() == id;
});
if (it != tasks_.end()) {
(*it)->complete();
std::cout << "Completed task " << id << std::endl;
return true;
}
return false;
}
void removeTask(int id) {
std::lock_guard<std::mutex> lock(mutex_);
tasks_.erase(
std::remove_if(tasks_.begin(), tasks_.end(),
[id](const std::unique_ptr<Task>& task) {
return task->getId() == id;
}),
tasks_.end());
std::cout << "Removed task " << id << std::endl;
}
std::vector<Task> getTasks() const {
std::lock_guard<std::mutex> lock(mutex_);
std::vector<Task> result;
result.reserve(tasks_.size());
for (const auto& task : tasks_) {
result.emplace_back(*task);
}
return result;
}
template<typename Predicate>
std::vector<Task> getFilteredTasks(Predicate pred) const {
std::lock_guard<std::mutex> lock(mutex_);
std::vector<Task> result;
for (const auto& task : tasks_) {
if (pred(*task)) {
result.emplace_back(*task);
}
}
return result;
}
size_t getTaskCount() const {
std::lock_guard<std::mutex> lock(mutex_);
return tasks_.size();
}
size_t getCompletedCount() const {
std::lock_guard<std::mutex> lock(mutex_);
return std::count_if(tasks_.begin(), tasks_.end(),
[](const std::unique_ptr<Task>& task) {
return task->isCompleted();
});
}
};
// Command pattern for task operations
class Command {
public:
virtual ~Command() = default;
virtual void execute() = 0;
virtual std::string getDescription() const = 0;
};
class AddTaskCommand : public Command {
private:
TaskManager& manager_;
std::string description_;
public:
AddTaskCommand(TaskManager& manager, std::string description)
: manager_(manager), description_(std::move(description)) {}
void execute() override {
manager_.addTask(description_);
}
std::string getDescription() const override {
return "Add task: " + description_;
}
};
class CompleteTaskCommand : public Command {
private:
TaskManager& manager_;
int task_id_;
public:
CompleteTaskCommand(TaskManager& manager, int id)
: manager_(manager), task_id_(id) {}
void execute() override {
manager_.completeTask(task_id_);
}
std::string getDescription() const override {
return "Complete task " + std::to_string(task_id_);
}
};
// Command processor with async execution
class CommandProcessor {
private:
std::vector<std::unique_ptr<Command>> commands_;
public:
void addCommand(std::unique_ptr<Command> command) {
commands_.push_back(std::move(command));
}
void executeAll() {
std::vector<std::future<void>> futures;
for (auto& command : commands_) {
futures.push_back(
std::async(std::launch::async, [&command]() {
std::cout << "Executing: " << command->getDescription() << std::endl;
command->execute();
})
);
}
// Wait for all commands to complete
for (auto& future : futures) {
future.wait();
}
commands_.clear();
}
};
// Statistics calculator using algorithms
class TaskStatistics {
public:
struct Stats {
size_t total_tasks;
size_t completed_tasks;
size_t pending_tasks;
double completion_rate;
std::string most_common_word;
};
static Stats calculate(const std::vector<Task>& tasks) {
Stats stats{};
stats.total_tasks = tasks.size();
stats.completed_tasks = std::count_if(tasks.begin(), tasks.end(),
[](const Task& task) { return task.isCompleted(); });
stats.pending_tasks = stats.total_tasks - stats.completed_tasks;
stats.completion_rate = stats.total_tasks > 0
? static_cast<double>(stats.completed_tasks) / stats.total_tasks * 100.0
: 0.0;
// Find most common word in descriptions
std::map<std::string, int> word_count;
for (const auto& task : tasks) {
std::istringstream iss(task.getDescription());
std::string word;
while (iss >> word) {
// Simple word processing (convert to lowercase)
std::transform(word.begin(), word.end(), word.begin(), ::tolower);
word_count[word]++;
}
}
if (!word_count.empty()) {
auto max_element = std::max_element(word_count.begin(), word_count.end(),
[](const auto& a, const auto& b) {
return a.second < b.second;
});
stats.most_common_word = max_element->first;
}
return stats;
}
};
// Main application
class TaskApp {
private:
TaskManager manager_;
CommandProcessor processor_;
void printMenu() {
std::cout << "\n=== Task Management System ===" << std::endl;
std::cout << "1. Add task" << std::endl;
std::cout << "2. Complete task" << std::endl;
std::cout << "3. Remove task" << std::endl;
std::cout << "4. List all tasks" << std::endl;
std::cout << "5. List pending tasks" << std::endl;
std::cout << "6. Show statistics" << std::endl;
std::cout << "7. Batch operations demo" << std::endl;
std::cout << "0. Exit" << std::endl;
std::cout << "Choice: ";
}
void batchOperationsDemo() {
std::cout << "\n=== Batch Operations Demo ===" << std::endl;
// Add commands to processor
processor_.addCommand(std::make_unique<AddTaskCommand>(manager_, "Review code"));
processor_.addCommand(std::make_unique<AddTaskCommand>(manager_, "Write tests"));
processor_.addCommand(std::make_unique<AddTaskCommand>(manager_, "Update documentation"));
processor_.addCommand(std::make_unique<AddTaskCommand>(manager_, "Deploy to staging"));
// Execute all commands asynchronously
processor_.executeAll();
std::cout << "Batch operations completed!" << std::endl;
}
public:
void run() {
// Pre-populate with some sample tasks
manager_.addTask("Learn modern C++");
manager_.addTask("Implement task manager");
manager_.addTask("Write comprehensive tests");
manager_.completeTask(1); // Complete first task
int choice;
while (true) {
printMenu();
std::cin >> choice;
switch (choice) {
case 1: {
std::cout << "Enter task description: ";
std::cin.ignore();
std::string description;
std::getline(std::cin, description);
manager_.addTask(description);
break;
}
case 2: {
std::cout << "Enter task ID to complete: ";
int id;
std::cin >> id;
if (!manager_.completeTask(id)) {
std::cout << "Task not found!" << std::endl;
}
break;
}
case 3: {
std::cout << "Enter task ID to remove: ";
int id;
std::cin >> id;
manager_.removeTask(id);
break;
}
case 4: {
auto tasks = manager_.getTasks();
std::cout << "\nAll tasks:" << std::endl;
for (const auto& task : tasks) {
std::cout << " " << task << std::endl;
}
break;
}
case 5: {
auto pending_tasks = manager_.getFilteredTasks(
[](const Task& task) { return !task.isCompleted(); });
std::cout << "\nPending tasks:" << std::endl;
for (const auto& task : pending_tasks) {
std::cout << " " << task << std::endl;
}
break;
}
case 6: {
auto tasks = manager_.getTasks();
auto stats = TaskStatistics::calculate(tasks);
std::cout << "\n=== Statistics ===" << std::endl;
std::cout << "Total tasks: " << stats.total_tasks << std::endl;
std::cout << "Completed: " << stats.completed_tasks << std::endl;
std::cout << "Pending: " << stats.pending_tasks << std::endl;
std::cout << "Completion rate: " << std::fixed << std::setprecision(1)
<< stats.completion_rate << "%" << std::endl;
if (!stats.most_common_word.empty()) {
std::cout << "Most common word: " << stats.most_common_word << std::endl;
}
break;
}
case 7: {
batchOperationsDemo();
break;
}
case 0: {
std::cout << "Goodbye!" << std::endl;
return;
}
default: {
std::cout << "Invalid choice!" << std::endl;
break;
}
}
}
}
};
int main() {
try {
std::cout << "=== Advanced C++ Features Demo ===" << std::endl;
// Demonstrate coroutines (if C++20 is available)
#ifdef __cpp_impl_coroutine
demonstrateCoroutines();
#endif
// Demonstrate ranges (if C++20 is available)
#if __cplusplus >= 202002L
demonstrateRanges();
#endif
// Demonstrate modules conceptually
demonstrateModulesConceptually();
std::cout << "\n=== Task Management Application ===" << std::endl;
TaskApp app;
app.run();
} catch (const std::exception& e) {
std::cerr << "Error: " << e.what() << std::endl;
return 1;
} catch (...) {
std::cerr << "Unknown error occurred" << std::endl;
return 1;
}
return 0;
}

Conclusion and Resources

Summary of Key Concepts

Throughout this comprehensive tutorial, we’ve covered the essential aspects of modern C++ programming:

Key Takeaways:

  • Memory Management: Use RAII and smart pointers for automatic resource management
  • Templates: Leverage generic programming for reusable, type-safe code
  • Modern Features: Embrace auto, lambdas, range-based loops, and move semantics
  • Performance: Understand memory layout, cache effects, and compiler optimizations
  • Design Patterns: Apply proven solutions to common programming problems
  • Exception Safety: Write robust code with proper exception handling
  • Concurrency: Use modern threading facilities for parallel programming

Coding Best Practices

Modern C++ Best Practices:

  1. Prefer stack allocation over heap allocation
  2. Use const correctness everywhere
  3. Embrace RAII for resource management
  4. Prefer algorithms over hand-written loops
  5. Use strong typing and avoid implicit conversions
  6. Write exception-safe code
  7. Follow the Rule of Zero/Three/Five
  8. Use modern C++ features appropriately

Additional Resources and Further Reading

Recommended Books:

  • Effective Modern C++ by Scott Meyers
  • C++ Concurrency in Action by Anthony Williams
  • The C++ Programming Language by Bjarne Stroustrup
  • Modern C++ Design by Andrei Alexandrescu
  • Clean Code by Robert C. Martin

Online Resources:

  • cppreference.com: Comprehensive C++ reference
  • CppCon: Annual C++ conference with excellent talks
  • ISO C++ Website: Official standards and guidelines
  • Compiler Explorer: Online tool for examining generated assembly
  • Quick Bench: Online C++ benchmarking tool

Exercise Suggestions

Programming Exercises:

  1. Implement a thread-safe cache with LRU eviction policy
  2. Create a compile-time JSON parser using template metaprogramming
  3. Build a memory pool allocator for high-performance applications
  4. Design a generic graph library with different traversal algorithms
  5. Implement a lock-free queue using atomic operations
  6. Create a functional programming library with map, filter, and reduce
  7. Build a compile-time unit system (meters, seconds, etc.)
  8. Implement a custom smart pointer with debugging capabilities

Compilation and Toolchain

Compiler Flags and Optimization

Common compiler flags for modern C++ development:

Terminal window
# Debug build
g++ -std=c++20 -Wall -Wextra -g -O0 -fsanitize=address,undefined
# Release build
g++ -std=c++20 -Wall -Wextra -O3 -DNDEBUG -march=native
# Profile-guided optimization
g++ -std=c++20 -O3 -fprofile-generate
# Run program to generate profile data
g++ -std=c++20 -O3 -fprofile-use
# Static analysis
clang++ -std=c++20 -Wall -Wextra --analyze
# Cross-platform considerations
g++ -std=c++20 -Wall -Wextra -pthread # Linux
cl /std:c++20 /W4 # Windows MSVC

Common Pitfalls and Solutions

Common Memory Errors:

  • Double deletion
  • Memory leaks
  • Dangling pointers
  • Buffer overruns
  • Use after free
  • Uninitialized memory access

Template Complications:

  • Template instantiation bloat
  • Compilation time explosion
  • Error message complexity
  • ODR violations with templates
  • SFINAE failures

Performance Guidelines

Performance Best Practices:

  • Profile before optimizing
  • Prefer algorithms over hand-written loops
  • Use appropriate data structures
  • Consider memory layout and cache effects
  • Minimize dynamic allocations
  • Use move semantics appropriately
  • Consider compile-time computation
  • Leverage SIMD when appropriate

End of Tutorial

This comprehensive tutorial has covered the essential aspects of modern C++ programming. Continue practicing these concepts and exploring advanced topics to become proficient in professional C++ development.

Happy coding!