Advanced Modern C++ Programming
Advanced Modern C++ Programming: A Comprehensive University Course
Table of Contents
- Introduction to Modern C++
- Memory Model and Pointers
- Dynamic Memory Management
- Smart Pointers and Modern Memory Management
- Template Fundamentals
- Advanced Template Techniques
- Object-Oriented Programming Advanced Concepts
- Modern C++ Features
- Exception Handling and RAII
- Concurrency and Threading
- Best Practices and Design Patterns
- Performance Optimization
- Advanced Topics and Modern Features
- Complete Application Example
- 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 declarationsint 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 parameterint 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 ownershipstd::shared_ptr
: Shared ownership with reference countingstd::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 templatetemplate<typename T>T maximum(T a, T b) { return (a > b) ? a : b;}
// Template with multiple parameterstemplate<typename T, typename U>auto add(T a, U b) -> decltype(a + b) { return a + b;}
// Template specializationtemplate<>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 parameterstemplate<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>::typesafeDivide(T a, T b) { if (b == 0) { throw std::runtime_error("Division by zero"); } return a / b;}
// Variadic templatestemplate<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 factorialtemplate<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 traitstemplate<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() methodtemplate<typename Container>typename std::enable_if<has_size_method<Container>::value, size_t>::typegetSize(const Container& c) { return c.size();}
// Template for arraystemplate<typename T, size_t N>constexpr size_t getSize(const T (&)[N]) { return N;}
// Variadic template for tuple-like operationstemplate<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 typestemplate<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 templatetemplate<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 pointerstemplate<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::stringtemplate<>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 conceptstemplate<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 conceptstemplate<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 requirementstemplate<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 functionsclass 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 classesclass 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 problemclass 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 problemclass 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 swimclass 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 exampletemplate<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 handlingclass 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 operationsclass 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 classclass 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 handlingvoid 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 resourcesclass 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 mutexclass 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 variablesclass 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 exampleclass 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 exampleclass 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 implementationclass 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 codeclass 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 sleeppublic: 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 Patternclass 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 Patternclass 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 Patternclass 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 Patternclass 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 Patternclass 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 structuresstruct 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 friendlyprivate: 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 allocationstemplate<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 testingclass 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 optimizationclass 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 coroutinetemplate<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 coroutinesGenerator<int> fibonacci() { int a = 0, b = 1; while (true) { co_yield a; auto temp = a; a = b; b = temp + b; }}
// Range generatorGenerator<int> range(int start, int end, int step = 1) { for (int i = start; i < end; i += step) { co_yield i; }}
// Async task coroutinetemplate<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 typestruct 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 exampleTask<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; }}#elsevoid 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 Systemclass 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 managerclass 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 operationsclass 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 executionclass 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 algorithmsclass 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 applicationclass 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:
- Prefer stack allocation over heap allocation
- Use const correctness everywhere
- Embrace RAII for resource management
- Prefer algorithms over hand-written loops
- Use strong typing and avoid implicit conversions
- Write exception-safe code
- Follow the Rule of Zero/Three/Five
- 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:
- Implement a thread-safe cache with LRU eviction policy
- Create a compile-time JSON parser using template metaprogramming
- Build a memory pool allocator for high-performance applications
- Design a generic graph library with different traversal algorithms
- Implement a lock-free queue using atomic operations
- Create a functional programming library with map, filter, and reduce
- Build a compile-time unit system (meters, seconds, etc.)
- Implement a custom smart pointer with debugging capabilities
Compilation and Toolchain
Compiler Flags and Optimization
Common compiler flags for modern C++ development:
# Debug buildg++ -std=c++20 -Wall -Wextra -g -O0 -fsanitize=address,undefined
# Release buildg++ -std=c++20 -Wall -Wextra -O3 -DNDEBUG -march=native
# Profile-guided optimizationg++ -std=c++20 -O3 -fprofile-generate# Run program to generate profile datag++ -std=c++20 -O3 -fprofile-use
# Static analysisclang++ -std=c++20 -Wall -Wextra --analyze
# Cross-platform considerationsg++ -std=c++20 -Wall -Wextra -pthread # Linuxcl /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!