Advanced C++
Master advanced C++ concepts and modern features
Introduction to Advanced C++
Templates & Generic Programming
Templates are C++'s powerful feature for generic programming, allowing you to write code that works with multiple types while maintaining type safety and performance. They enable compile-time polymorphism and are the foundation of the Standard Template Library (STL).
Why Templates Matter:
Code Reusability
Write once, use with any type. No code duplication needed.
Zero Runtime Cost
Template instantiation happens at compile-time with no performance penalty.
Type Safety
Compile-time type checking prevents runtime errors.
Compile-time Computation
Enable template metaprogramming for advanced optimizations.
Function Templates:
#include <iostream>
#include <vector>
#include <algorithm>
#include <type_traits>
using namespace std;
// Basic function template
template<typename T>
T maximum(T a, T b) {
return (a > b) ? a : b;
}
// Function template with multiple parameters
template<typename T, typename U>
auto add(T a, U b) -> decltype(a + b) {
return a + b;
}
// Function template with default template parameter
template<typename T = int>
T square(T value) {
return value * value;
}
// Variadic function template (C++11)
template<typename T>
T sum(T value) {
return value;
}
template<typename T, typename... Args>
T sum(T first, Args... args) {
return first + sum(args...);
}
// Template with SFINAE (Substitution Failure Is Not An Error)
template<typename T>
typename enable_if<is_arithmetic<T>::value, T>::type
safeAdd(T a, T b) {
return a + b;
}
// Template specialization
template<>
const char* maximum<const char*>(const char* a, const char* b) {
return (strcmp(a, b) > 0) ? a : b;
}
// Function template with concept (C++20)
#if __cplusplus >= 202002L
#include <concepts>
template<std::integral T>
T gcd(T a, T b) {
while (b != 0) {
T temp = b;
b = a % b;
a = temp;
}
return a;
}
#endif
int main() {
// Basic usage
cout << "Maximum of 10, 20: " << maximum(10, 20) << endl;
cout << "Maximum of 3.14, 2.71: " << maximum(3.14, 2.71) << endl;
// Multiple parameter types
cout << "Add 5 + 3.14: " << add(5, 3.14) << endl;
// Default template parameter
cout << "Square of 5: " << square(5) << endl;
cout << "Square of 3.14: " << square(3.14) << endl;
// Variadic templates
cout << "Sum of 1,2,3,4,5: " << sum(1, 2, 3, 4, 5) << endl;
cout << "Sum of 1.1,2.2,3.3: " << sum(1.1, 2.2, 3.3) << endl;
// SFINAE example
cout << "Safe add 10 + 20: " << safeAdd(10, 20) << endl;
// Template specialization
cout << "Max string: " << maximum("apple", "banana") << endl;
#if __cplusplus >= 202002L
// Concepts (C++20)
cout << "GCD of 48, 18: " << gcd(48, 18) << endl;
#endif
return 0;
}
Class Templates:
#include <iostream>
#include <vector>
#include <memory>
#include <stdexcept>
using namespace std;
// Basic class template
template<typename T>
class Container {
private:
vector<T> data;
public:
void add(const T& item) { data.push_back(item); }
T& get(size_t index) { return data.at(index); }
size_t size() const { return data.size(); }
bool empty() const { return data.empty(); }
// Template member function
template<typename U>
void addConverted(const U& item) {
data.push_back(static_cast<T>(item));
}
};
// Template with multiple parameters and default values
template<typename T, size_t Size = 10>
class FixedArray {
private:
T data[Size];
size_t currentSize = 0;
public:
void push_back(const T& value) {
if (currentSize >= Size) {
throw overflow_error("Array is full");
}
data[currentSize++] = value;
}
T& operator[](size_t index) {
if (index >= currentSize) {
throw out_of_range("Index out of range");
}
return data[index];
}
size_t size() const { return currentSize; }
size_t capacity() const { return Size; }
};
// Template specialization for bool (space optimization)
template<size_t Size>
class FixedArray<bool, Size> {
private:
vector<bool> data; // std::vector<bool> is specialized for space efficiency
public:
FixedArray() : data(Size, false) {}
void set(size_t index, bool value) {
if (index >= Size) throw out_of_range("Index out of range");
data[index] = value;
}
bool get(size_t index) const {
if (index >= Size) throw out_of_range("Index out of range");
return data[index];
}
size_t size() const { return Size; }
};
// Advanced: Smart pointer-like template
template<typename T>
class UniquePtr {
private:
T* ptr;
public:
explicit UniquePtr(T* p = nullptr) : ptr(p) {}
~UniquePtr() { delete ptr; }
// Move constructor
UniquePtr(UniquePtr&& other) noexcept : ptr(other.ptr) {
other.ptr = nullptr;
}
// Move assignment
UniquePtr& operator=(UniquePtr&& other) noexcept {
if (this != &other) {
delete ptr;
ptr = other.ptr;
other.ptr = nullptr;
}
return *this;
}
// Disable copy
UniquePtr(const UniquePtr&) = delete;
UniquePtr& operator=(const UniquePtr&) = delete;
T& operator*() const { return *ptr; }
T* operator->() const { return ptr; }
T* get() const { return ptr; }
bool operator!() const { return !ptr; }
explicit operator bool() const { return ptr != nullptr; }
T* release() {
T* temp = ptr;
ptr = nullptr;
return temp;
}
void reset(T* p = nullptr) {
delete ptr;
ptr = p;
}
};
// Template template parameter
template<typename T, template<typename> class Container>
class Stack {
private:
Container<T> container;
public:
void push(const T& item) { container.add(item); }
T pop() {
if (container.empty()) {
throw runtime_error("Stack is empty");
}
T item = container.get(container.size() - 1);
// Note: This is simplified - real implementation would need pop_back
return item;
}
bool empty() const { return container.empty(); }
size_t size() const { return container.size(); }
};
int main() {
// Basic class template usage
Container<int> intContainer;
intContainer.add(10);
intContainer.add(20);
intContainer.addConverted(3.14); // Template member function
cout << "Container size: " << intContainer.size() << endl;
cout << "First element: " << intContainer.get(0) << endl;
// Fixed array template
FixedArray<string, 5> names;
names.push_back("Alice");
names.push_back("Bob");
names.push_back("Charlie");
cout << "Names array size: " << names.size() << endl;
cout << "First name: " << names[0] << endl;
// Template specialization for bool
FixedArray<bool, 8> flags;
flags.set(0, true);
flags.set(3, true);
flags.set(7, true);
cout << "Flag at index 0: " << flags.get(0) << endl;
cout << "Flag at index 1: " << flags.get(1) << endl;
// Smart pointer template
UniquePtr<int> smartPtr(new int(42));
cout << "Smart pointer value: " << *smartPtr << endl;
// Move semantics
UniquePtr<string> ptr1(new string("Hello"));
UniquePtr<string> ptr2 = move(ptr1);
cout << "Moved string: " << *ptr2 << endl;
// Template template parameter
Stack<int, Container> stack;
stack.push(1);
stack.push(2);
stack.push(3);
cout << "Stack size: " << stack.size() << endl;
return 0;
}
Template Concepts (C++20):
Concepts provide a way to specify constraints on template parameters, making template code more readable and providing better error messages.
// C++20 Concepts Example
#include <iostream>
#include <concepts>
#include <type_traits>
// Define custom concepts
template<typename T>
concept Numeric = std::integral<T> || std::floating_point<T>;
template<typename T>
concept Printable = requires(T t) {
std::cout << t;
};
template<typename T>
concept Comparable = requires(T a, T b) {
{ a < b } -> std::convertible_to<bool>;
{ a > b } -> std::convertible_to<bool>;
{ a == b } -> std::convertible_to<bool>;
};
// Using concepts with functions
template<Numeric T>
T multiply(T a, T b) {
return a * b;
}
template<Printable T>
void print(const T& value) {
std::cout << value << std::endl;
}
template<Comparable T>
T findMax(T a, T b, T c) {
T max = a;
if (b > max) max = b;
if (c > max) max = c;
return max;
}
// Concepts with classes
template<Numeric T>
class Calculator {
public:
T add(T a, T b) { return a + b; }
T subtract(T a, T b) { return a - b; }
T multiply(T a, T b) { return a * b; }
T divide(T a, T b) {
static_assert(std::floating_point<T>, "Division requires floating point type");
return a / b;
}
};
// Abbreviated function template syntax (C++20)
auto quickAdd(Numeric auto a, Numeric auto b) {
return a + b;
}
int main() {
// Valid uses with concepts
std::cout << multiply(5, 3) << std::endl; // int
std::cout << multiply(2.5, 1.5) << std::endl; // double
print("Hello World"); // string literal
print(42); // int
print(3.14); // double
std::cout << findMax(10, 5, 8) << std::endl;
std::cout << findMax(3.14, 2.71, 1.41) << std::endl;
Calculator<double> calc;
std::cout << calc.add(10.5, 5.5) << std::endl;
std::cout << calc.divide(10.0, 3.0) << std::endl;
// Abbreviated syntax
std::cout << quickAdd(100, 200) << std::endl;
std::cout << quickAdd(1.1, 2.2) << std::endl;
// The following would cause compilation errors:
// multiply("hello", "world"); // string is not Numeric
// Calculator<std::string> calc; // string is not Numeric
return 0;
}
Template Best Practices:
Do's
- Use meaningful template parameter names
- Provide default template arguments when appropriate
- Use SFINAE or concepts for constraints
- Specialize templates for specific types when needed
- Use auto for return type deduction
- Prefer variadic templates over overloading
Don'ts
- Don't use templates when simple functions suffice
- Avoid deep template instantiation hierarchies
- Don't ignore compilation time impact
- Avoid exposing template internals in headers
- Don't use templates for every small function
- Avoid complex SFINAE when concepts are available
Pro Tip: Templates are evaluated at compile-time, so complex template code can significantly increase compilation time. Use explicit instantiation and precompiled headers for frequently used templates.
Performance: Well-designed templates can produce code that's as fast as hand-written specialized code, sometimes even faster due to compiler optimizations enabled by template instantiation.
Smart Pointers & Memory Management
Smart pointers are modern C++ objects that automatically manage memory, providing exception safety and preventing common memory-related bugs like memory leaks, double deletion, and dangling pointers. They represent ownership semantics and are fundamental to RAII (Resource Acquisition Is Initialization).
Memory Management Evolution:
❌ C-style (Dangerous)
int* ptr = malloc(sizeof(int));
Manual memory management, prone to leaks
⚠️ Raw Pointers (Error-prone)
int* ptr = new int(42);
Must remember to delete, exception-unsafe
✅ Smart Pointers (Modern)
auto ptr = std::make_unique<int>(42);
Automatic cleanup, exception-safe, expressive
Types of Smart Pointers:
unique_ptr
Exclusive Ownership
- Single owner of the resource
- Move-only semantics
- Zero overhead when optimized
- Automatic deletion when destroyed
shared_ptr
Shared Ownership
- Multiple owners allowed
- Reference counting
- Thread-safe reference counting
- Deleted when last owner is destroyed
weak_ptr
Non-owning Observer
- Observes shared_ptr without owning
- Breaks circular references
- Can check if resource still exists
- Converts to shared_ptr when needed
Comprehensive Smart Pointer Examples:
#include <iostream>
#include <memory>
#include <vector>
#include <string>
#include <cassert>
using namespace std;
// Resource class for demonstration
class Resource {
private:
string name;
static int counter;
int id;
public:
Resource(const string& n) : name(n), id(++counter) {
cout << "Resource [" << id << "] '" << name << "' created" << endl;
}
~Resource() {
cout << "Resource [" << id << "] '" << name << "' destroyed" << endl;
}
void use() const {
cout << "Using resource [" << id << "] '" << name << "'" << endl;
}
const string& getName() const { return name; }
int getId() const { return id; }
};
int Resource::counter = 0;
// Factory function using unique_ptr
unique_ptr<Resource> createResource(const string& name) {
return make_unique<Resource>(name);
}
// Function that takes ownership
void processResource(unique_ptr<Resource> resource) {
if (resource) {
resource->use();
cout << "Processing completed for " << resource->getName() << endl;
}
// resource automatically destroyed when function ends
}
// Function that observes without taking ownership
void observeResource(const Resource* resource) {
if (resource) {
cout << "Observing resource: " << resource->getName() << endl;
}
}
// Example class using shared_ptr for aggregation
class ResourceManager {
private:
vector<shared_ptr<Resource>> resources;
public:
void addResource(shared_ptr<Resource> resource) {
resources.push_back(resource);
cout << "Added resource to manager. Total: " << resources.size() << endl;
}
void useAllResources() const {
cout << "Using all managed resources:" << endl;
for (const auto& resource : resources) {
if (resource) {
resource->use();
}
}
}
shared_ptr<Resource> getResource(int index) const {
if (index >= 0 && index < resources.size()) {
return resources[index];
}
return nullptr;
}
size_t size() const { return resources.size(); }
};
// Example of circular reference problem and solution
class Parent;
class Child;
class Parent {
public:
string name;
vector<shared_ptr<Child>> children;
Parent(const string& n) : name(n) {
cout << "Parent " << name << " created" << endl;
}
~Parent() {
cout << "Parent " << name << " destroyed" << endl;
}
void addChild(shared_ptr<Child> child);
};
class Child {
public:
string name;
weak_ptr<Parent> parent; // Use weak_ptr to break circular reference
Child(const string& n) : name(n) {
cout << "Child " << name << " created" << endl;
}
~Child() {
cout << "Child " << name << " destroyed" << endl;
}
void setParent(shared_ptr<Parent> p) {
parent = p;
}
void visitParent() {
if (auto p = parent.lock()) { // Convert weak_ptr to shared_ptr
cout << "Child " << name << " visiting parent " << p->name << endl;
} else {
cout << "Child " << name << "'s parent is no longer available" << endl;
}
}
};
void Parent::addChild(shared_ptr<Child> child) {
children.push_back(child);
child->setParent(shared_from_this()); // This requires Parent to inherit from enable_shared_from_this
}
// Custom deleter example
void customDeleter(Resource* ptr) {
cout << "Custom deleter called for " << ptr->getName() << endl;
delete ptr;
}
int main() {
cout << "=== UNIQUE_PTR EXAMPLES ===" << endl;
// Basic unique_ptr usage
{
cout << "\n--- Basic Usage ---" << endl;
auto resource1 = make_unique<Resource>("Database Connection");
resource1->use();
// Transfer ownership
auto resource2 = move(resource1);
assert(resource1 == nullptr); // resource1 is now null
if (resource2) {
resource2->use();
}
// Factory function
auto resource3 = createResource("Network Socket");
processResource(move(resource3)); // Transfer ownership to function
assert(resource3 == nullptr); // resource3 is now null
}
cout << "\n=== SHARED_PTR EXAMPLES ===" << endl;
// Basic shared_ptr usage
{
cout << "\n--- Shared Ownership ---" << endl;
auto resource = make_shared<Resource>("Shared File Handle");
cout << "Reference count: " << resource.use_count() << endl;
ResourceManager manager;
manager.addResource(resource); // Manager shares ownership
cout << "Reference count after adding to manager: " << resource.use_count() << endl;
{
auto anotherRef = resource; // Another shared reference
cout << "Reference count with additional ref: " << resource.use_count() << endl;
anotherRef->use();
} // anotherRef goes out of scope
cout << "Reference count after scope exit: " << resource.use_count() << endl;
manager.useAllResources();
}
cout << "\n=== WEAK_PTR EXAMPLES ===" << endl;
// Circular reference prevention
{
cout << "\n--- Breaking Circular References ---" << endl;
auto parent = make_shared<Parent>("Alice");
auto child1 = make_shared<Child>("Bob");
auto child2 = make_shared<Child>("Carol");
// Note: This simplified example doesn't use enable_shared_from_this
// In real code, Parent should inherit from enable_shared_from_this<Parent>
parent->children.push_back(child1);
parent->children.push_back(child2);
child1->setParent(parent);
child2->setParent(parent);
cout << "Parent reference count: " << parent.use_count() << endl;
cout << "Child1 reference count: " << child1.use_count() << endl;
child1->visitParent();
child2->visitParent();
// When parent goes out of scope, children can still check if parent exists
{
auto tempChild = child1;
parent.reset(); // Release parent
tempChild->visitParent(); // Should show parent is no longer available
}
}
cout << "\n=== ADVANCED FEATURES ===" << endl;
// Custom deleter
{
cout << "\n--- Custom Deleter ---" << endl;
unique_ptr<Resource, decltype(&customDeleter)> customPtr(
new Resource("Custom Managed"), customDeleter);
customPtr->use();
}
// Array handling
{
cout << "\n--- Array Handling ---" << endl;
auto intArray = make_unique<int[]>(5);
for (int i = 0; i < 5; ++i) {
intArray[i] = i * i;
}
cout << "Array values: ";
for (int i = 0; i < 5; ++i) {
cout << intArray[i] << " ";
}
cout << endl;
}
// Performance comparison
{
cout << "\n--- Performance Notes ---" << endl;
cout << "unique_ptr size: " << sizeof(unique_ptr<Resource>) << " bytes" << endl;
cout << "shared_ptr size: " << sizeof(shared_ptr<Resource>) << " bytes" << endl;
cout << "weak_ptr size: " << sizeof(weak_ptr<Resource>) << " bytes" << endl;
cout << "raw pointer size: " << sizeof(Resource*) << " bytes" << endl;
}
return 0;
}
Smart Pointer Decision Tree:
🤔 Which Smart Pointer to Use?
unique_ptr
shared_ptr
weak_ptr
Common Patterns & Best Practices:
Factory Pattern
return make_unique<T>(args...);
Return unique_ptr from factory functions
Ownership Transfer
processData(std::move(ptr));
Use std::move to transfer unique_ptr ownership
Safe Observation
if (auto p = weak_ptr.lock()) { ... }
Always check weak_ptr before using
Exception Safety
auto ptr = make_unique<T>();
Prefer make_unique/make_shared over new
Performance Considerations:
Pointer Type | Memory Overhead | Performance | Thread Safety | Use Case |
---|---|---|---|---|
unique_ptr | Zero overhead | Same as raw pointer | Not thread-safe | Exclusive ownership |
shared_ptr | 2 pointers + ref count | Atomic operations | Reference counting is thread-safe | Shared ownership |
weak_ptr | 2 pointers | Lock operation overhead | Thread-safe observation | Non-owning observation |
Avoid These Mistakes:
- Don't use
shared_ptr
whenunique_ptr
suffices - Never store
shared_ptr
in member variables without considering cycles - Don't convert raw pointers to smart pointers randomly
- Avoid
get()
unless interfacing with C APIs
Memory Safety Achievement: With smart pointers, you've eliminated manual memory management. Your code is now exception-safe, leak-free, and expresses ownership semantics clearly!
Exception Handling
Exception handling in C++ provides a structured way to handle runtime errors and exceptional conditions. It separates error handling code from normal program logic, making code more readable and maintainable while ensuring program stability.
Exception Handling Philosophy:
🎯 Separation of Concerns
Error handling logic is separated from business logic
🔄 Stack Unwinding
Automatic cleanup of local objects when exceptions occur
🛡️ Exception Safety
Guarantees about program state when exceptions are thrown
⚡ Performance
Zero-cost when no exceptions are thrown
Exception Safety Levels:
Basic Guarantee
No resource leaks, objects remain in valid state
Strong Guarantee
Operation succeeds completely or has no effect
No-throw Guarantee
Operation never throws exceptions
#include <iostream>
#include <stdexcept>
#include <string>
#include <vector>
#include <memory>
#include <fstream>
#include <system_error>
using namespace std;
// Custom exception hierarchy
class MathException : public std::exception {
protected:
string message;
public:
MathException(const string& msg) : message(msg) {}
const char* what() const noexcept override { return message.c_str(); }
};
class DivisionByZeroException : public MathException {
public:
DivisionByZeroException() : MathException("Division by zero is not allowed!") {}
};
class NegativeArgumentException : public MathException {
public:
NegativeArgumentException(const string& operation)
: MathException(operation + " is not defined for negative numbers") {}
};
class OverflowException : public MathException {
public:
OverflowException(const string& operation)
: MathException(operation + " result is too large to compute") {}
};
// RAII resource management class
class FileManager {
private:
string filename;
unique_ptr<ofstream> file;
public:
FileManager(const string& fname) : filename(fname) {
file = make_unique<ofstream>(filename);
if (!file->is_open()) {
throw runtime_error("Failed to open file: " + filename);
}
cout << "File opened: " << filename << endl;
}
~FileManager() {
if (file && file->is_open()) {
file->close();
cout << "File closed: " << filename << endl;
}
}
void write(const string& data) {
if (!file || !file->is_open()) {
throw runtime_error("File is not open for writing");
}
*file << data << endl;
if (file->fail()) {
throw runtime_error("Failed to write to file");
}
}
};
// Exception-safe calculator class
class SafeCalculator {
public:
// Strong exception safety guarantee
static double divide(double a, double b) {
if (b == 0.0) {
throw DivisionByZeroException();
}
return a / b;
}
// Strong exception safety guarantee
static long long factorial(int n) {
if (n < 0) {
throw NegativeArgumentException("Factorial");
}
if (n > 20) {
throw OverflowException("Factorial");
}
long long result = 1;
for (int i = 1; i <= n; i++) {
result *= i;
}
return result;
}
// Function with multiple exception types
static double power(double base, int exponent) {
if (base == 0.0 && exponent < 0) {
throw DivisionByZeroException();
}
if (exponent < 0) {
throw NegativeArgumentException("Negative exponent");
}
double result = 1.0;
for (int i = 0; i < exponent; i++) {
result *= base;
if (result > 1e100) { // Simple overflow check
throw OverflowException("Power");
}
}
return result;
}
};
// Container with exception safety
class SafeVector {
private:
vector<int> data;
public:
// Strong exception safety
void push_back_safe(int value) {
data.push_back(value); // If this throws, object state unchanged
}
// Basic exception safety
void reserve_space(size_t size) {
try {
data.reserve(size);
} catch (const bad_alloc& e) {
cout << "Memory allocation failed: " << e.what() << endl;
throw; // Re-throw to caller
}
}
// No-throw guarantee
size_t size() const noexcept {
return data.size();
}
// Strong exception safety with rollback
void batch_insert(const vector<int>& values) {
size_t original_size = data.size();
try {
for (int value : values) {
data.push_back(value);
}
} catch (...) {
// Rollback to original state
data.resize(original_size);
throw;
}
}
void print() const {
cout << "Vector contents: ";
for (int val : data) {
cout << val << " ";
}
cout << endl;
}
};
// Function demonstrating exception specifications (C++11)
void noThrowFunction() noexcept {
cout << "This function guarantees not to throw" << endl;
// If an exception is thrown here, std::terminate is called
}
// Modern exception handling patterns
class ModernExceptionExample {
public:
// Using std::optional instead of exceptions for expected failures
static optional<double> safe_divide_optional(double a, double b) {
if (b == 0.0) {
return nullopt; // No exception, just empty optional
}
return a / b;
}
// Using expected-like pattern (C++23 will have std::expected)
struct Result {
bool success;
double value;
string error_message;
static Result success_result(double val) {
return {true, val, ""};
}
static Result error_result(const string& msg) {
return {false, 0.0, msg};
}
};
static Result safe_divide_result(double a, double b) {
if (b == 0.0) {
return Result::error_result("Division by zero");
}
return Result::success_result(a / b);
}
};
int main() {
cout << "=== COMPREHENSIVE EXCEPTION HANDLING ===" << endl;
// Basic exception handling with custom hierarchy
cout << "\n--- Custom Exception Hierarchy ---" << endl;
try {
double result = SafeCalculator::divide(10, 0);
cout << "Result: " << result << endl;
}
catch (const DivisionByZeroException& e) {
cout << "Division by zero: " << e.what() << endl;
}
catch (const MathException& e) {
cout << "Math error: " << e.what() << endl;
}
catch (const exception& e) {
cout << "General exception: " << e.what() << endl;
}
// Exception safety with RAII
cout << "\n--- RAII and Exception Safety ---" << endl;
try {
FileManager file("test.txt");
file.write("Hello, World!");
file.write("This is a test file.");
// Simulate an error
throw runtime_error("Simulated error after file operations");
}
catch (const runtime_error& e) {
cout << "Runtime error: " << e.what() << endl;
cout << "Note: File was automatically closed due to RAII" << endl;
}
// Multiple exception types from single function
cout << "\n--- Multiple Exception Types ---" << endl;
vector<pair<double, int>> test_cases = {{2.0, 10}, {0.0, -1}, {2.0, 50}};
for (const auto& [base, exp] : test_cases) {
try {
double result = SafeCalculator::power(base, exp);
cout << base << "^" << exp << " = " << result << endl;
}
catch (const DivisionByZeroException& e) {
cout << "Division by zero in power calculation: " << e.what() << endl;
}
catch (const NegativeArgumentException& e) {
cout << "Negative argument: " << e.what() << endl;
}
catch (const OverflowException& e) {
cout << "Overflow: " << e.what() << endl;
}
}
// Exception safety levels
cout << "\n--- Exception Safety Guarantees ---" << endl;
SafeVector vec;
try {
vec.push_back_safe(1);
vec.push_back_safe(2);
vec.push_back_safe(3);
vec.print();
// This might throw, but will rollback
vector<int> large_batch(1000000, 42); // Very large batch
vec.batch_insert(large_batch);
}
catch (const bad_alloc& e) {
cout << "Memory allocation failed, but vector state preserved" << endl;
vec.print(); // Should show original content
}
catch (...) {
cout << "Unknown exception caught" << endl;
}
// Modern alternatives to exceptions
cout << "\n--- Modern Alternatives ---" << endl;
// Using optional
if (auto result = ModernExceptionExample::safe_divide_optional(10, 2)) {
cout << "Division result: " << *result << endl;
} else {
cout << "Division failed (returned empty optional)" << endl;
}
// Using result pattern
auto result = ModernExceptionExample::safe_divide_result(10, 0);
if (result.success) {
cout << "Division result: " << result.value << endl;
} else {
cout << "Division failed: " << result.error_message << endl;
}
// Nested try-catch
cout << "\n--- Nested Exception Handling ---" << endl;
try {
try {
long long fact = SafeCalculator::factorial(-1);
cout << "Factorial: " << fact << endl;
}
catch (const NegativeArgumentException& e) {
cout << "Caught negative argument, retrying with positive..." << endl;
long long fact = SafeCalculator::factorial(5);
cout << "Factorial of 5: " << fact << endl;
}
}
catch (const exception& e) {
cout << "Final catch: " << e.what() << endl;
}
// No-throw function
noThrowFunction();
cout << "\n=== Exception handling demonstration complete ===" << endl;
return 0;
}
Exception Handling Best Practices:
Do's
- Use RAII for automatic resource cleanup
- Catch exceptions by const reference
- Create meaningful exception hierarchies
- Provide strong exception safety guarantees
- Use noexcept for functions that don't throw
- Consider alternatives like std::optional
Don'ts
- Don't use exceptions for normal control flow
- Don't throw exceptions from destructors
- Don't catch exceptions you can't handle
- Don't use raw pointers with exceptions
- Don't ignore exception safety guarantees
- Don't use exceptions in performance-critical code
Performance Note: Exceptions have zero cost when not thrown, but throwing and catching exceptions can be expensive. Use them for exceptional conditions, not regular program flow.
Modern Trend: Many modern C++ codebases are moving toward alternatives like std::optional, std::expected (C++23), and error codes for better performance and explicit error handling.
Lambda Expressions & Functional Programming
Lambda expressions provide a concise way to define anonymous functions, forming the foundation of functional programming in C++.
Lambda Expressions
#include <iostream>
#include <vector>
#include <algorithm>
#include <functional>
using namespace std;
int main() {
// Basic lambda
cout << "=== Basic Lambda ===" << endl;
auto greet = []() {
cout << "Hello from lambda!" << endl;
};
greet();
// Lambda with parameters
auto add = [](int a, int b) {
return a + b;
};
cout << "5 + 3 = " << add(5, 3) << endl;
// Lambda with capture
cout << "\n=== Lambda with Capture ===" << endl;
int multiplier = 10;
// Capture by value
auto multiplyByValue = [multiplier](int x) {
return x * multiplier;
};
// Capture by reference
auto multiplyByRef = [&multiplier](int x) {
multiplier++; // Can modify the original variable
return x * multiplier;
};
cout << "Multiply 5 by value: " << multiplyByValue(5) << endl;
cout << "Multiplier before ref call: " << multiplier << endl;
cout << "Multiply 5 by ref: " << multiplyByRef(5) << endl;
cout << "Multiplier after ref call: " << multiplier << endl;
// Using lambdas with STL algorithms
cout << "\n=== Lambda with STL Algorithms ===" << endl;
vector<int> numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
// Find even numbers
cout << "Even numbers: ";
for_each(numbers.begin(), numbers.end(), [](int n) {
if (n % 2 == 0) {
cout << n << " ";
}
});
cout << endl;
// Transform elements
vector<int> squared;
transform(numbers.begin(), numbers.end(), back_inserter(squared),
[](int n) { return n * n; });
cout << "Squared numbers: ";
for (int n : squared) {
cout << n << " ";
}
cout << endl;
// Count elements with condition
int count = count_if(numbers.begin(), numbers.end(),
[](int n) { return n > 5; });
cout << "Numbers greater than 5: " << count << endl;
// Generic lambda (C++14)
cout << "\n=== Generic Lambda ===" << endl;
auto genericAdd = [](auto a, auto b) {
return a + b;
};
cout << "Generic add (int): " << genericAdd(5, 3) << endl;
cout << "Generic add (double): " << genericAdd(5.5, 3.2) << endl;
cout << "Generic add (string): " << genericAdd(string("Hello "), string("World")) << endl;
return 0;
}
Functional Programming Concepts
Functional programming is a programming paradigm that treats computation as the evaluation of mathematical functions, avoiding state and mutable data.
#include <iostream>
#include <vector>
#include <algorithm>
#include <functional>
#include <numeric>
#include <optional>
#include <string>
#include <map>
using namespace std;
// ===== HIGHER-ORDER FUNCTIONS =====
// Functions that take other functions as parameters or return functions
// Function that takes a function as parameter
template<typename Func>
void applyToRange(int start, int end, Func func) {
for (int i = start; i <= end; ++i) {
func(i);
}
}
// Function that returns a function
auto createMultiplier(int factor) {
return [factor](int x) { return x * factor; };
}
// Curry function - transforms function with multiple arguments into chain of functions
auto curry_add = [](int x) {
return [x](int y) {
return x + y;
};
};
// ===== PURE FUNCTIONS =====
// Functions with no side effects - same input always produces same output
int pure_add(int a, int b) {
return a + b; // No side effects, deterministic
}
// Not pure - has side effects
int counter = 0;
int impure_add(int a, int b) {
counter++; // Side effect - modifies global state
return a + b;
}
// ===== IMMUTABLE DATA STRUCTURES =====
class ImmutableVector {
private:
vector<int> data;
public:
ImmutableVector(const vector<int>& vec) : data(vec) {}
// Returns new vector instead of modifying existing one
ImmutableVector append(int value) const {
vector<int> newData = data;
newData.push_back(value);
return ImmutableVector(newData);
}
ImmutableVector filter(function<bool(int)> predicate) const {
vector<int> result;
copy_if(data.begin(), data.end(), back_inserter(result), predicate);
return ImmutableVector(result);
}
template<typename Func>
auto map(Func transformer) const {
vector<decltype(transformer(data[0]))> result;
transform(data.begin(), data.end(), back_inserter(result), transformer);
return result;
}
int reduce(function<int(int, int)> reducer, int initial = 0) const {
return accumulate(data.begin(), data.end(), initial, reducer);
}
void print() const {
for (int val : data) {
cout << val << " ";
}
cout << endl;
}
size_t size() const { return data.size(); }
int operator[](size_t index) const { return data[index]; }
};
// ===== MONADS (Optional as example) =====
template<typename T>
class Maybe {
private:
optional<T> value;
public:
Maybe() : value(nullopt) {}
Maybe(T val) : value(val) {}
bool hasValue() const { return value.has_value(); }
T getValue() const { return value.value(); }
// Bind operation (flatMap)
template<typename Func>
auto bind(Func func) const {
if (hasValue()) {
return func(getValue());
} else {
return Maybe<decltype(func(getValue()).getValue())>();
}
}
// Map operation
template<typename Func>
auto map(Func func) const {
if (hasValue()) {
return Maybe<decltype(func(getValue()))>(func(getValue()));
} else {
return Maybe<decltype(func(getValue()))>();
}
}
};
// Helper function to create Maybe
template<typename T>
Maybe<T> some(T value) {
return Maybe<T>(value);
}
template<typename T>
Maybe<T> none() {
return Maybe<T>();
}
// Safe division that returns Maybe
Maybe<double> safeDivide(double a, double b) {
if (b != 0) {
return some(a / b);
} else {
return none<double>();
}
}
// ===== FUNCTION COMPOSITION =====
template<typename F, typename G>
auto compose(F f, G g) {
return [f, g](auto x) {
return f(g(x));
};
}
// Pipe operator simulation
template<typename T, typename Func>
auto operator|(T&& value, Func func) {
return func(forward<T>(value));
}
int main() {
cout << "=== Functional Programming in C++ ===" << endl;
// ===== HIGHER-ORDER FUNCTIONS =====
cout << "\n1. Higher-Order Functions:" << endl;
// Function as parameter
cout << "Numbers 1-5: ";
applyToRange(1, 5, [](int x) { cout << x << " "; });
cout << endl;
// Function returning function
auto multiplyBy3 = createMultiplier(3);
cout << "5 * 3 = " << multiplyBy3(5) << endl;
// Currying
auto add5 = curry_add(5);
cout << "Curried add: 5 + 10 = " << add5(10) << endl;
// ===== MAP, FILTER, REDUCE =====
cout << "\n2. Map, Filter, Reduce:" << endl;
vector<int> numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
// Map: Transform each element
vector<int> squares;
transform(numbers.begin(), numbers.end(), back_inserter(squares),
[](int x) { return x * x; });
cout << "Squares: ";
for (int sq : squares) cout << sq << " ";
cout << endl;
// Filter: Select elements based on condition
vector<int> evens;
copy_if(numbers.begin(), numbers.end(), back_inserter(evens),
[](int x) { return x % 2 == 0; });
cout << "Even numbers: ";
for (int even : evens) cout << even << " ";
cout << endl;
// Reduce: Combine all elements into single value
int sum = accumulate(numbers.begin(), numbers.end(), 0);
int product = accumulate(numbers.begin(), numbers.end(), 1,
[](int acc, int x) { return acc * x; });
cout << "Sum: " << sum << ", Product: " << product << endl;
// ===== IMMUTABLE DATA STRUCTURES =====
cout << "\n3. Immutable Data Structures:" << endl;
ImmutableVector vec({1, 2, 3, 4, 5});
cout << "Original vector: ";
vec.print();
auto vec2 = vec.append(6).append(7);
cout << "After append (original unchanged): ";
vec.print();
cout << "New vector: ";
vec2.print();
auto evenVec = vec2.filter([](int x) { return x % 2 == 0; });
cout << "Even numbers: ";
evenVec.print();
auto doubled = vec.map([](int x) { return x * 2; });
cout << "Doubled: ";
for (int val : doubled) cout << val << " ";
cout << endl;
int totalSum = vec2.reduce([](int acc, int x) { return acc + x; });
cout << "Sum using reduce: " << totalSum << endl;
// ===== MONADS (Maybe) =====
cout << "\n4. Monads (Maybe/Optional):" << endl;
auto result1 = safeDivide(10, 2);
auto result2 = safeDivide(10, 0);
if (result1.hasValue()) {
cout << "10 / 2 = " << result1.getValue() << endl;
}
if (!result2.hasValue()) {
cout << "10 / 0 = undefined (safely handled)" << endl;
}
// Chaining operations with Maybe
auto chainedResult = some(20.0)
.bind([](double x) { return safeDivide(x, 4); })
.bind([](double x) { return safeDivide(x, 2.5); })
.map([](double x) { return x * 2; });
if (chainedResult.hasValue()) {
cout << "Chained operations result: " << chainedResult.getValue() << endl;
}
// ===== FUNCTION COMPOSITION =====
cout << "\n5. Function Composition:" << endl;
auto addOne = [](int x) { return x + 1; };
auto multiplyBy2 = [](int x) { return x * 2; };
auto toString = [](int x) { return to_string(x); };
// Compose functions
auto addOneAndDouble = compose(multiplyBy2, addOne);
cout << "addOneAndDouble(5): " << addOneAndDouble(5) << endl;
// Pipeline-style composition
string result = 5
| addOne
| multiplyBy2
| toString;
cout << "Pipeline result: " << result << endl;
// ===== RECURSION vs ITERATION =====
cout << "\n6. Functional Recursion:" << endl;
// Functional factorial
function<int(int)> factorial = [&factorial](int n) {
return (n <= 1) ? 1 : n * factorial(n - 1);
};
cout << "Factorial of 5: " << factorial(5) << endl;
// Tail-recursive version (more efficient)
function<int(int, int)> tailFactorial = [&tailFactorial](int n, int acc) {
return (n <= 1) ? acc : tailFactorial(n - 1, n * acc);
};
cout << "Tail recursive factorial of 5: " << tailFactorial(5, 1) << endl;
return 0;
}
Functional Programming Principles
Immutability
Data structures that cannot be modified after creation, reducing bugs and enabling safe concurrent programming.
Pure Functions
Functions with no side effects that always return the same output for the same input, making code predictable and testable.
Higher-Order Functions
Functions that accept other functions as parameters or return functions, enabling powerful abstraction patterns.
Function Composition
Combining simple functions to build more complex operations, promoting code reuse and modularity.
Recursion
Solving problems by breaking them into smaller subproblems, often more elegant than iterative solutions.
Monads
Design patterns for handling computations with context (like null values, errors) in a composable way.
Common Functional Algorithms
Map
Transform each element in a collection using a function
transform(begin, end, result, function)
Filter
Select elements from a collection based on a predicate
copy_if(begin, end, result, predicate)
Reduce/Fold
Combine all elements into a single value using an accumulator
accumulate(begin, end, initial, binary_op)
Zip
Combine elements from multiple collections pairwise
transform(begin1, end1, begin2, result, binary_op)
Functional Programming Best Practices:
- Prefer Pure Functions: Write functions without side effects when possible
- Use Immutable Data: Create new objects instead of modifying existing ones
- Compose Functions: Build complex operations from simple, reusable functions
- Avoid Shared Mutable State: Minimize global variables and shared data
- Use STL Algorithms: Leverage existing functional algorithms in <algorithm>
- Consider Performance: Be aware of the overhead of functional approaches
- Mix Paradigms: Combine functional and object-oriented approaches as appropriate
Move Semantics & Perfect Forwarding
Move semantics allow efficient transfer of resources from temporary objects, while perfect forwarding preserves the value category of function arguments.
Move Semantics
#include <iostream>
#include <vector>
#include <string>
#include <utility>
#include <cstring>
using namespace std;
class MyString {
private:
char* data;
size_t length;
public:
// Constructor
MyString(const char* str = "") {
length = strlen(str);
data = new char[length + 1];
strcpy(data, str);
cout << "Constructor: " << data << endl;
}
// Copy constructor
MyString(const MyString& other) {
length = other.length;
data = new char[length + 1];
strcpy(data, other.data);
cout << "Copy constructor: " << data << endl;
}
// Move constructor
MyString(MyString&& other) noexcept {
data = other.data;
length = other.length;
other.data = nullptr;
other.length = 0;
cout << "Move constructor: " << data << endl;
}
// Copy assignment operator
MyString& operator=(const MyString& other) {
if (this != &other) {
delete[] data;
length = other.length;
data = new char[length + 1];
strcpy(data, other.data);
cout << "Copy assignment: " << data << endl;
}
return *this;
}
// Move assignment operator
MyString& operator=(MyString&& other) noexcept {
if (this != &other) {
delete[] data;
data = other.data;
length = other.length;
other.data = nullptr;
other.length = 0;
cout << "Move assignment: " << data << endl;
}
return *this;
}
// Destructor
~MyString() {
if (data) {
cout << "Destructor: " << data << endl;
delete[] data;
} else {
cout << "Destructor: (moved object)" << endl;
}
}
const char* c_str() const { return data ? data : ""; }
};
MyString createString(const char* str) {
return MyString(str); // Return by value - move constructor called
}
int main() {
cout << "=== Move Semantics Demo ===" << endl;
// Regular construction
MyString str1("Hello");
// Copy construction
MyString str2 = str1;
// Move construction
MyString str3 = move(str1); // str1 is now in moved-from state
cout << "str1 after move: '" << str1.c_str() << "'" << endl;
cout << "str3 after move: '" << str3.c_str() << "'" << endl;
// Function returning by value (move optimization)
cout << "\n=== Function Return ===" << endl;
MyString str4 = createString("World");
// Using std::move with containers
cout << "\n=== Container Move ===" << endl;
vector<MyString> vec;
vec.push_back(MyString("First")); // Move constructor
vec.push_back(MyString("Second")); // Move constructor
MyString str5("Third");
vec.push_back(move(str5)); // Explicit move
cout << "str5 after move to vector: '" << str5.c_str() << "'" << endl;
return 0;
}
Perfect Forwarding
Perfect forwarding allows template functions to pass arguments to another function while preserving their value category (lvalue or rvalue). This is essential for writing efficient generic code.
#include <iostream>
#include <utility>
#include <string>
#include <vector>
#include <memory>
using namespace std;
// ===== DEMONSTRATION CLASS =====
class Resource {
private:
string name;
public:
// Constructor
Resource(const string& n) : name(n) {
cout << "Resource created: " << name << endl;
}
// Copy constructor
Resource(const Resource& other) : name(other.name + "_copy") {
cout << "Resource copied: " << name << endl;
}
// Move constructor
Resource(Resource&& other) noexcept : name(move(other.name)) {
cout << "Resource moved: " << name << endl;
other.name = "moved_from";
}
// Destructor
~Resource() {
cout << "Resource destroyed: " << name << endl;
}
const string& getName() const { return name; }
};
// ===== WITHOUT PERFECT FORWARDING (PROBLEMATIC) =====
void processResource(const Resource& res) {
cout << "Processing const Resource: " << res.getName() << endl;
}
void processResource(Resource&& res) {
cout << "Processing moved Resource: " << res.getName() << endl;
}
// Bad wrapper - doesn't preserve value category
template<typename T>
void badWrapper(T arg) { // Always copies!
processResource(arg);
}
// Better wrapper but still not perfect
template<typename T>
void betterWrapper(T& arg) { // Only works with lvalues
processResource(arg);
}
// ===== PERFECT FORWARDING SOLUTION =====
template<typename T>
void perfectWrapper(T&& arg) { // Universal/Forwarding reference
processResource(forward<T>(arg)); // Perfect forwarding!
}
// ===== UNIVERSAL REFERENCES IN ACTION =====
template<typename T>
void analyzeType(T&& param) {
cout << "Parameter type analysis:" << endl;
if constexpr (is_lvalue_reference_v<T>) {
cout << " - T is lvalue reference" << endl;
cout << " - param is lvalue" << endl;
} else {
cout << " - T is rvalue reference or value type" << endl;
cout << " - param is rvalue" << endl;
}
}
// ===== PERFECT FORWARDING IN CONSTRUCTORS =====
template<typename T>
class Wrapper {
private:
T wrapped;
public:
// Perfect forwarding constructor
template<typename U>
Wrapper(U&& value) : wrapped(forward<U>(value)) {
cout << "Wrapper constructed with perfect forwarding" << endl;
}
T& get() { return wrapped; }
const T& get() const { return wrapped; }
};
// ===== FACTORY FUNCTION WITH PERFECT FORWARDING =====
template<typename T, typename... Args>
unique_ptr<T> make_unique_perfect(Args&&... args) {
return unique_ptr<T>(new T(forward<Args>(args)...));
}
// Class for factory demonstration
class ComplexObject {
private:
string name;
int value;
Resource resource;
public:
ComplexObject(const string& n, int v, Resource r)
: name(n), value(v), resource(move(r)) {
cout << "ComplexObject created: " << name << endl;
}
~ComplexObject() {
cout << "ComplexObject destroyed: " << name << endl;
}
void display() const {
cout << "ComplexObject: " << name << ", value: " << value
<< ", resource: " << resource.getName() << endl;
}
};
// ===== VARIADIC TEMPLATE WITH PERFECT FORWARDING =====
template<typename Func, typename... Args>
auto invokeFunction(Func&& func, Args&&... args)
-> decltype(func(forward<Args>(args)...)) {
cout << "Invoking function with " << sizeof...(args) << " arguments" << endl;
return func(forward<Args>(args)...);
}
// Function to be invoked
int multiply(int a, int b, int c) {
return a * b * c;
}
string concatenate(const string& a, const string& b, const string& c) {
return a + " " + b + " " + c;
}
int main() {
cout << "=== Perfect Forwarding Demonstration ===" << endl;
// ===== BASIC PERFECT FORWARDING =====
cout << "\n1. Basic Perfect Forwarding:" << endl;
Resource res1("original");
cout << "\nUsing bad wrapper (always copies):" << endl;
badWrapper(res1); // Copies even though we pass lvalue
cout << "\nUsing perfect wrapper:" << endl;
perfectWrapper(res1); // Correctly identifies as lvalue
perfectWrapper(Resource("temporary")); // Correctly identifies as rvalue
// ===== TYPE ANALYSIS =====
cout << "\n2. Type Analysis with Universal References:" << endl;
Resource res2("analysis_test");
cout << "\nPassing lvalue:" << endl;
analyzeType(res2);
cout << "\nPassing rvalue:" << endl;
analyzeType(Resource("temp_for_analysis"));
// ===== PERFECT FORWARDING IN CONSTRUCTORS =====
cout << "\n3. Perfect Forwarding in Constructors:" << endl;
Resource res3("for_wrapper");
cout << "\nCreating wrapper with lvalue:" << endl;
Wrapper<Resource> wrapper1(res3); // Should copy
cout << "\nCreating wrapper with rvalue:" << endl;
Wrapper<Resource> wrapper2(Resource("for_wrapper_move")); // Should move
// ===== FACTORY FUNCTION =====
cout << "\n4. Factory Function with Perfect Forwarding:" << endl;
Resource factoryRes("factory_resource");
cout << "\nCreating ComplexObject via factory:" << endl;
auto complexObj = make_unique_perfect<ComplexObject>(
"MyObject",
42,
move(factoryRes) // Perfect forwarding preserves move
);
complexObj->display();
// ===== VARIADIC TEMPLATES WITH PERFECT FORWARDING =====
cout << "\n5. Variadic Templates with Perfect Forwarding:" << endl;
// Invoke function with different argument types
int a = 2, b = 3, c = 4;
int result1 = invokeFunction(multiply, a, b, c); // lvalues
cout << "Multiply result: " << result1 << endl;
string result2 = invokeFunction(concatenate,
string("Hello"),
string("Perfect"),
string("Forwarding")); // rvalues
cout << "Concatenate result: " << result2 << endl;
// ===== PERFECT FORWARDING WITH LAMBDAS =====
cout << "\n6. Perfect Forwarding with Lambdas:" << endl;
auto perfectLambda = [](auto&& func, auto&&... args) {
cout << "Lambda perfect forwarding" << endl;
return func(forward<decltype(args)>(args)...);
};
int lambdaResult = perfectLambda(
[](int x, int y) { return x + y; },
10,
20
);
cout << "Lambda result: " << lambdaResult << endl;
// ===== REFERENCE COLLAPSING DEMONSTRATION =====
cout << "\n7. Reference Collapsing Rules:" << endl;
cout << "T& + & = T& (lvalue ref)" << endl;
cout << "T& + && = T& (lvalue ref)" << endl;
cout << "T&& + & = T& (lvalue ref)" << endl;
cout << "T&& + && = T&& (rvalue ref)" << endl;
return 0;
}
Key Perfect Forwarding Concepts
Universal References
Template parameters with && that can bind to both lvalues and rvalues, enabling perfect forwarding.
std::forward
Conditionally casts arguments to preserve their original value category when forwarding.
Reference Collapsing
Rules that determine the final reference type when combining multiple reference types.
Template Argument Deduction
Compiler automatically deduces template types based on the arguments passed to the function.
Factory Functions
Functions that create objects while perfectly forwarding constructor arguments.
Variadic Templates
Templates that accept variable numbers of arguments, often used with perfect forwarding.
Perfect Forwarding Best Practices:
- Use Universal References: Template parameter T&& for forwarding references
- Always Use std::forward: Don't forward without std::forward<T>
- Understand Reference Collapsing: Know how && + & = & works
- Template Constructors: Use perfect forwarding in constructors for efficiency
- Factory Functions: Perfect forwarding essential for make_unique, emplace_back, etc.
- Combine with Move Semantics: Perfect forwarding and move semantics work together
- Watch Out for Overloads: Perfect forwarding can interfere with overload resolution
Modern C++ Features
Modern C++ (C++11/14/17/20) introduces many powerful features.
#include <iostream>
#include <vector>
#include <map>
#include <string>
#include <optional>
#include <variant>
#include <any>
#include <tuple>
using namespace std;
int main() {
// Auto keyword
cout << "=== Auto Keyword ===" << endl;
auto x = 42; // int
auto y = 3.14; // double
auto z = "Hello"; // const char*
cout << "x: " << x << ", y: " << y << ", z: " << z << endl;
// Range-based for loops
cout << "\n=== Range-based For Loop ===" << endl;
vector<int> numbers = {1, 2, 3, 4, 5};
for (const auto& num : numbers) {
cout << num << " ";
}
cout << endl;
// Initializer lists
cout << "\n=== Initializer Lists ===" << endl;
vector<string> names{"Alice", "Bob", "Charlie"};
map<string, int> ages{{"Alice", 25}, {"Bob", 30}, {"Charlie", 35}};
for (const auto& [name, age] : ages) { // Structured binding (C++17)
cout << name << " is " << age << " years old" << endl;
}
// nullptr
cout << "\n=== nullptr ===" << endl;
int* ptr = nullptr;
if (ptr == nullptr) {
cout << "Pointer is null" << endl;
}
// Optional (C++17)
cout << "\n=== std::optional ===" << endl;
auto findValue = [](const vector<int>& vec, int target) -> optional<size_t> {
for (size_t i = 0; i < vec.size(); ++i) {
if (vec[i] == target) {
return i;
}
}
return nullopt;
};
if (auto index = findValue(numbers, 3)) {
cout << "Found 3 at index: " << *index << endl;
} else {
cout << "Value not found" << endl;
}
// Variant (C++17)
cout << "\n=== std::variant ===" << endl;
variant<int, string, double> var;
var = 42;
cout << "Variant holds int: " << get<int>(var) << endl;
var = "Hello World";
cout << "Variant holds string: " << get<string>(var) << endl;
// Tuple and structured binding
cout << "\n=== Tuple and Structured Binding ===" << endl;
auto person = make_tuple("John", 28, 75000.0);
auto [name, age, salary] = person;
cout << "Name: " << name << ", Age: " << age << ", Salary: $" << salary << endl;
// Constexpr
cout << "\n=== constexpr ===" << endl;
constexpr auto factorial = [](int n) {
return (n <= 1) ? 1 : n * factorial(n - 1);
};
constexpr int fact5 = factorial(5); // Computed at compile time
cout << "5! = " << fact5 << endl;
// if constexpr (C++17)
cout << "\n=== if constexpr ===" << endl;
auto processValue = []<typename T>(T value) {
if constexpr (is_integral_v<T>) {
cout << "Processing integer: " << value << endl;
} else if constexpr (is_floating_point_v<T>) {
cout << "Processing floating point: " << value << endl;
} else {
cout << "Processing other type" << endl;
}
};
processValue(42);
processValue(3.14);
processValue("Hello");
return 0;
}
Multithreading & Concurrency
C++11 introduced built-in support for multithreading and concurrent programming.
Multithreading Basics
Multithreading allows programs to execute multiple threads concurrently, improving performance on multi-core systems.
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <future>
#include <vector>
#include <chrono>
using namespace std;
// Global variables for demonstration
mutex mtx;
condition_variable cv;
bool ready = false;
int counter = 0;
// Simple thread function
void printNumbers(int start, int end, const string& threadName) {
for (int i = start; i <= end; ++i) {
{
lock_guard<mutex> lock(mtx);
cout << threadName << ": " << i << endl;
}
this_thread::sleep_for(chrono::milliseconds(100));
}
}
// Thread-safe counter increment
void incrementCounter(int times) {
for (int i = 0; i < times; ++i) {
lock_guard<mutex> lock(mtx);
++counter;
}
}
// Producer-Consumer example
void producer() {
this_thread::sleep_for(chrono::seconds(1));
{
lock_guard<mutex> lock(mtx);
ready = true;
cout << "Producer: Data is ready!" << endl;
}
cv.notify_one();
}
void consumer() {
unique_lock<mutex> lock(mtx);
cv.wait(lock, [] { return ready; });
cout << "Consumer: Processing data..." << endl;
}
// Function for async/future example
int calculateSum(int start, int end) {
int sum = 0;
for (int i = start; i <= end; ++i) {
sum += i;
}
this_thread::sleep_for(chrono::milliseconds(500)); // Simulate work
return sum;
}
int main() {
// Basic thread creation and joining
cout << "=== Basic Threading ===" << endl;
thread t1(printNumbers, 1, 5, "Thread1");
thread t2(printNumbers, 6, 10, "Thread2");
t1.join();
t2.join();
// Thread-safe operations
cout << "\n=== Thread-Safe Counter ===" << endl;
counter = 0; // Reset counter
vector<thread> threads;
for (int i = 0; i < 5; ++i) {
threads.emplace_back(incrementCounter, 1000);
}
for (auto& t : threads) {
t.join();
}
cout << "Final counter value: " << counter << endl;
// Producer-Consumer pattern
cout << "\n=== Producer-Consumer ===" << endl;
ready = false; // Reset
thread producerThread(producer);
thread consumerThread(consumer);
producerThread.join();
consumerThread.join();
// Async and Future
cout << "\n=== Async and Future ===" << endl;
// Launch async tasks
auto future1 = async(launch::async, calculateSum, 1, 1000);
auto future2 = async(launch::async, calculateSum, 1001, 2000);
auto future3 = async(launch::async, calculateSum, 2001, 3000);
cout << "Calculating sums asynchronously..." << endl;
// Get results
int sum1 = future1.get();
int sum2 = future2.get();
int sum3 = future3.get();
cout << "Sum 1-1000: " << sum1 << endl;
cout << "Sum 1001-2000: " << sum2 << endl;
cout << "Sum 2001-3000: " << sum3 << endl;
cout << "Total sum: " << (sum1 + sum2 + sum3) << endl;
// Thread with lambda
cout << "\n=== Thread with Lambda ===" << endl;
thread lambdaThread([]() {
for (int i = 0; i < 3; ++i) {
cout << "Lambda thread: " << i << endl;
this_thread::sleep_for(chrono::milliseconds(200));
}
});
lambdaThread.join();
cout << "All threads completed!" << endl;
return 0;
}
Concurrency Concepts
Concurrency is about dealing with multiple tasks at once, while parallelism is about executing multiple tasks simultaneously. C++ provides various tools for concurrent programming.
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <atomic>
#include <queue>
#include <memory>
#include <future>
#include <chrono>
#include <random>
using namespace std;
// ===== ATOMIC OPERATIONS =====
atomic<int> atomicCounter{0};
int regularCounter = 0;
mutex counterMutex;
void atomicIncrement(int iterations) {
for (int i = 0; i < iterations; ++i) {
atomicCounter.fetch_add(1, memory_order_relaxed);
}
}
void regularIncrement(int iterations) {
for (int i = 0; i < iterations; ++i) {
lock_guard<mutex> lock(counterMutex);
++regularCounter;
}
}
// ===== THREAD-SAFE QUEUE =====
template<typename T>
class ThreadSafeQueue {
private:
queue<T> queue_;
mutable mutex mutex_;
condition_variable condition_;
public:
void push(T item) {
lock_guard<mutex> lock(mutex_);
queue_.push(item);
condition_.notify_one();
}
bool tryPop(T& item) {
lock_guard<mutex> lock(mutex_);
if (queue_.empty()) {
return false;
}
item = queue_.front();
queue_.pop();
return true;
}
void waitAndPop(T& item) {
unique_lock<mutex> lock(mutex_);
condition_.wait(lock, [this] { return !queue_.empty(); });
item = queue_.front();
queue_.pop();
}
bool empty() const {
lock_guard<mutex> lock(mutex_);
return queue_.empty();
}
};
// ===== WORKER THREAD POOL SIMULATION =====
class SimpleTask {
public:
int id;
string description;
SimpleTask(int id, const string& desc) : id(id), description(desc) {}
void execute() {
cout << "Executing task " << id << ": " << description << endl;
// Simulate work
this_thread::sleep_for(chrono::milliseconds(100 + rand() % 200));
cout << "Task " << id << " completed!" << endl;
}
};
ThreadSafeQueue<shared_ptr<SimpleTask>> taskQueue;
atomic<bool> stopWorkers{false};
void worker(int workerId) {
cout << "Worker " << workerId << " started" << endl;
while (!stopWorkers) {
shared_ptr<SimpleTask> task;
if (taskQueue.tryPop(task)) {
cout << "Worker " << workerId << " picked up task" << endl;
task->execute();
} else {
this_thread::sleep_for(chrono::milliseconds(10));
}
}
cout << "Worker " << workerId << " stopped" << endl;
}
// ===== LOCK-FREE PROGRAMMING =====
class LockFreeCounter {
private:
atomic<int> count{0};
public:
void increment() {
count.fetch_add(1, memory_order_relaxed);
}
void decrement() {
count.fetch_sub(1, memory_order_relaxed);
}
int get() const {
return count.load(memory_order_relaxed);
}
// Compare and swap operation
bool compareAndSwap(int expected, int desired) {
return count.compare_exchange_weak(expected, desired, memory_order_relaxed);
}
};
// ===== SHARED STATE WITH READER-WRITER LOCKS =====
class SharedResource {
private:
mutable shared_mutex rwMutex;
vector<int> data;
public:
void write(int value) {
unique_lock<shared_mutex> lock(rwMutex);
data.push_back(value);
cout << "Written: " << value << " (size: " << data.size() << ")" << endl;
}
vector<int> read() const {
shared_lock<shared_mutex> lock(rwMutex);
cout << "Reading data (size: " << data.size() << ")" << endl;
return data;
}
size_t size() const {
shared_lock<shared_mutex> lock(rwMutex);
return data.size();
}
};
int main() {
cout << "=== Concurrency Patterns Demo ===" << endl;
// ===== ATOMIC OPERATIONS DEMO =====
cout << "\n1. Atomic vs Regular Operations:" << endl;
const int iterations = 10000;
const int numThreads = 4;
// Test atomic operations
vector<thread> atomicThreads;
auto start = chrono::high_resolution_clock::now();
for (int i = 0; i < numThreads; ++i) {
atomicThreads.emplace_back(atomicIncrement, iterations);
}
for (auto& t : atomicThreads) {
t.join();
}
auto atomicDuration = chrono::high_resolution_clock::now() - start;
cout << "Atomic counter result: " << atomicCounter.load() << endl;
// Test regular operations with mutex
vector<thread> regularThreads;
start = chrono::high_resolution_clock::now();
for (int i = 0; i < numThreads; ++i) {
regularThreads.emplace_back(regularIncrement, iterations);
}
for (auto& t : regularThreads) {
t.join();
}
auto regularDuration = chrono::high_resolution_clock::now() - start;
cout << "Regular counter result: " << regularCounter << endl;
// ===== WORKER THREAD POOL DEMO =====
cout << "\n2. Worker Thread Pool:" << endl;
// Start worker threads
vector<thread> workers;
for (int i = 0; i < 3; ++i) {
workers.emplace_back(worker, i + 1);
}
// Add tasks to queue
for (int i = 1; i <= 8; ++i) {
auto task = make_shared<SimpleTask>(i, "Process data batch " + to_string(i));
taskQueue.push(task);
}
// Let workers process tasks
this_thread::sleep_for(chrono::seconds(3));
// Stop workers
stopWorkers = true;
for (auto& w : workers) {
w.join();
}
// ===== LOCK-FREE PROGRAMMING DEMO =====
cout << "\n3. Lock-Free Programming:" << endl;
LockFreeCounter lockFreeCounter;
vector<thread> lockFreeThreads;
for (int i = 0; i < 4; ++i) {
lockFreeThreads.emplace_back([&lockFreeCounter]() {
for (int j = 0; j < 1000; ++j) {
lockFreeCounter.increment();
}
});
}
for (auto& t : lockFreeThreads) {
t.join();
}
cout << "Lock-free counter final value: " << lockFreeCounter.get() << endl;
// ===== READER-WRITER DEMO =====
cout << "\n4. Reader-Writer Shared Resource:" << endl;
SharedResource sharedResource;
// Writer threads
vector<thread> writers;
for (int i = 0; i < 2; ++i) {
writers.emplace_back([&sharedResource, i]() {
for (int j = 1; j <= 5; ++j) {
sharedResource.write(i * 10 + j);
this_thread::sleep_for(chrono::milliseconds(50));
}
});
}
// Reader threads
vector<thread> readers;
for (int i = 0; i < 3; ++i) {
readers.emplace_back([&sharedResource, i]() {
for (int j = 0; j < 3; ++j) {
auto data = sharedResource.read();
this_thread::sleep_for(chrono::milliseconds(30));
}
});
}
// Wait for all threads
for (auto& w : writers) {
w.join();
}
for (auto& r : readers) {
r.join();
}
cout << "\n=== Concurrency Demo Completed ===" << endl;
cout << "Final shared resource size: " << sharedResource.size() << endl;
return 0;
}
Key Concurrency Concepts:
Atomic Operations
Lock-free operations that are guaranteed to be performed atomically without race conditions.
Thread Pools
Pre-created threads that wait for tasks, improving performance by avoiding thread creation overhead.
Shared Memory
Memory accessible by multiple threads, requiring synchronization to prevent data races.
Lock-Free Programming
Programming without traditional locks, using atomic operations and memory ordering.
Reader-Writer Locks
Allow multiple readers or single writer access to shared resources for better performance.
Memory Ordering
Control over how memory operations are ordered across threads for correctness and performance.
Concurrency Best Practices:
- Minimize Shared State: Reduce the amount of data shared between threads
- Use Immutable Data: Prefer immutable objects that can be safely shared
- Avoid Data Races: Always synchronize access to shared mutable data
- Choose Right Synchronization: Use appropriate synchronization primitives for your use case
- Test Thoroughly: Concurrent bugs can be difficult to reproduce and debug
- Consider Lock-Free: For high-performance scenarios, consider lock-free algorithms
Template Metaprogramming
Template metaprogramming is a technique where templates are used to generate code at compile-time. It allows you to perform computations, make decisions, and generate specialized code during compilation, resulting in highly optimized runtime performance with zero runtime overhead.
Core Concepts:
🕰️ Compile-time Computation
Calculations performed during compilation, not runtime
🎯 Template Specialization
Different implementations for specific types or values
🔄 Recursive Templates
Templates that call themselves with modified parameters
✨ SFINAE
Substitution Failure Is Not An Error - conditional compilation
🎨 Type Traits
Compile-time type information and manipulation
🚀 constexpr
Modern compile-time evaluation with cleaner syntax
Classical Template Metaprogramming:
#include <iostream>
#include <type_traits>
#include <chrono>
#include <limits>
// ===== COMPILE-TIME FACTORIAL =====
template<int N>
struct Factorial {
static constexpr long long value = N * Factorial<N - 1>::value;
};
// Template specialization for base case
template<>
struct Factorial<0> {
static constexpr long long value = 1;
};
template<>
struct Factorial<1> {
static constexpr long long value = 1;
};
// ===== COMPILE-TIME FIBONACCI =====
template<int N>
struct Fibonacci {
static constexpr long long value = Fibonacci<N-1>::value + Fibonacci<N-2>::value;
};
template<>
struct Fibonacci<0> {
static constexpr long long value = 0;
};
template<>
struct Fibonacci<1> {
static constexpr long long value = 1;
};
// ===== COMPILE-TIME PRIME CHECKING =====
template<int N, int Divisor = N-1>
struct IsPrime {
static constexpr bool value = (N % Divisor != 0) && IsPrime<N, Divisor-1>::value;
};
template<int N>
struct IsPrime<N, 1> {
static constexpr bool value = true;
};
template<>
struct IsPrime<1, 0> {
static constexpr bool value = false;
};
template<>
struct IsPrime<2, 1> {
static constexpr bool value = true;
};
// ===== COMPILE-TIME POWER =====
template<int Base, int Exponent>
struct Power {
static constexpr long long value = Base * Power<Base, Exponent-1>::value;
};
template<int Base>
struct Power<Base, 0> {
static constexpr long long value = 1;
};
// ===== TYPE LIST MANIPULATION =====
// Basic type list
template<typename... Types>
struct TypeList {};
// Get length of type list
template<typename List>
struct Length;
template<typename... Types>
struct Length<TypeList<Types...>> {
static constexpr size_t value = sizeof...(Types);
};
// Get type at index
template<size_t Index, typename List>
struct TypeAt;
template<size_t Index, typename Head, typename... Tail>
struct TypeAt<Index, TypeList<Head, Tail...>> {
using type = typename TypeAt<Index-1, TypeList<Tail...>>::type;
};
template<typename Head, typename... Tail>
struct TypeAt<0, TypeList<Head, Tail...>> {
using type = Head;
};
// ===== COMPILE-TIME STRING PROCESSING =====
template<char... Chars>
struct String {
static constexpr size_t length = sizeof...(Chars);
static constexpr char data[length + 1] = {Chars..., '\0'};
};
template<char... Chars>
constexpr char String<Chars...>::data[String<Chars...>::length + 1];
// User-defined literal for compile-time strings
template<typename CharT, CharT... Chars>
constexpr String<Chars...> operator""_s() {
return {};
}
int main() {
std::cout << "=== CLASSICAL TEMPLATE METAPROGRAMMING ===" << std::endl;
// Compile-time calculations
constexpr auto fact5 = Factorial<5>::value;
constexpr auto fact10 = Factorial<10>::value;
constexpr auto fib10 = Fibonacci<10>::value;
constexpr auto fib20 = Fibonacci<20>::value;
std::cout << "\n--- Compile-time Calculations ---" << std::endl;
std::cout << "5! = " << fact5 << std::endl;
std::cout << "10! = " << fact10 << std::endl;
std::cout << "Fibonacci(10) = " << fib10 << std::endl;
std::cout << "Fibonacci(20) = " << fib20 << std::endl;
// Prime checking
std::cout << "\n--- Prime Checking ---" << std::endl;
std::cout << "Is 17 prime? " << std::boolalpha << IsPrime<17>::value << std::endl;
std::cout << "Is 18 prime? " << std::boolalpha << IsPrime<18>::value << std::endl;
std::cout << "Is 97 prime? " << std::boolalpha << IsPrime<97>::value << std::endl;
// Power calculations
constexpr auto pow2_10 = Power<2, 10>::value;
constexpr auto pow3_5 = Power<3, 5>::value;
std::cout << "\n--- Power Calculations ---" << std::endl;
std::cout << "2^10 = " << pow2_10 << std::endl;
std::cout << "3^5 = " << pow3_5 << std::endl;
// Type list operations
using MyTypes = TypeList<int, double, std::string, char>;
constexpr auto length = Length<MyTypes>::value;
using SecondType = TypeAt<1, MyTypes>::type; // double
std::cout << "\n--- Type List Operations ---" << std::endl;
std::cout << "Type list length: " << length << std::endl;
std::cout << "Second type size: " << sizeof(SecondType) << " bytes" << std::endl;
return 0;
}
Modern constexpr Approach (C++11/14/17):
#include <iostream>
#include <array>
#include <algorithm>
#include <numeric>
#include <string_view>
// ===== MODERN CONSTEXPR FUNCTIONS =====
// Factorial using constexpr function (much cleaner than template recursion)
constexpr long long factorial(int n) {
return (n <= 1) ? 1 : n * factorial(n - 1);
}
// Fibonacci with memoization at compile-time
constexpr long long fibonacci(int n) {
if (n <= 1) return n;
long long a = 0, b = 1;
for (int i = 2; i <= n; ++i) {
long long temp = a + b;
a = b;
b = temp;
}
return b;
}
// Prime checking with optimized algorithm
constexpr bool isPrime(int n) {
if (n < 2) return false;
if (n == 2) return true;
if (n % 2 == 0) return false;
for (int i = 3; i * i <= n; i += 2) {
if (n % i == 0) return false;
}
return true;
}
// Generate array of first N primes at compile-time
template<size_t N>
constexpr std::array<int, N> generatePrimes() {
std::array<int, N> primes{};
size_t count = 0;
int candidate = 2;
while (count < N) {
if (isPrime(candidate)) {
primes[count] = candidate;
++count;
}
++candidate;
}
return primes;
}
// Compile-time string hashing
constexpr size_t hashString(std::string_view str) {
size_t hash = 5381;
for (char c : str) {
hash = ((hash << 5) + hash) + static_cast<size_t>(c);
}
return hash;
}
// Compile-time mathematical operations on arrays
template<size_t N>
constexpr std::array<int, N> generateSquares() {
std::array<int, N> squares{};
for (size_t i = 0; i < N; ++i) {
squares[i] = static_cast<int>(i * i);
}
return squares;
}
// Compile-time sorting
template<size_t N>
constexpr std::array<int, N> bubbleSort(std::array<int, N> arr) {
for (size_t i = 0; i < N - 1; ++i) {
for (size_t j = 0; j < N - i - 1; ++j) {
if (arr[j] > arr[j + 1]) {
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
return arr;
}
// ===== COMPILE-TIME DECISION MAKING =====
// if constexpr examples (C++17)
template<typename T>
constexpr auto processValue(T value) {
if constexpr (std::is_integral_v<T>) {
return value * 2; // Double integers
} else if constexpr (std::is_floating_point_v<T>) {
return value * 1.5; // Multiply floats by 1.5
} else {
return value; // Return as-is for other types
}
}
// Compile-time polymorphism with concepts (C++20)
#if __cplusplus >= 202002L
#include <concepts>
template<std::integral T>
constexpr T power(T base, unsigned int exp) {
T result = 1;
for (unsigned int i = 0; i < exp; ++i) {
result *= base;
}
return result;
}
template<std::floating_point T>
constexpr T power(T base, unsigned int exp) {
T result = 1.0;
for (unsigned int i = 0; i < exp; ++i) {
result *= base;
}
return result;
}
#endif
int main() {
std::cout << "=== MODERN CONSTEXPR METAPROGRAMMING ===" << std::endl;
// Compile-time calculations with constexpr functions
constexpr auto fact5 = factorial(5);
constexpr auto fact10 = factorial(10);
constexpr auto fib15 = fibonacci(15);
constexpr auto fib25 = fibonacci(25);
std::cout << "\n--- constexpr Function Results ---" << std::endl;
std::cout << "5! = " << fact5 << std::endl;
std::cout << "10! = " << fact10 << std::endl;
std::cout << "Fibonacci(15) = " << fib15 << std::endl;
std::cout << "Fibonacci(25) = " << fib25 << std::endl;
// Compile-time prime generation
constexpr auto first10Primes = generatePrimes<10>();
std::cout << "\n--- First 10 Primes (compile-time generated) ---" << std::endl;
for (size_t i = 0; i < first10Primes.size(); ++i) {
std::cout << first10Primes[i];
if (i < first10Primes.size() - 1) std::cout << ", ";
}
std::cout << std::endl;
// Compile-time string hashing
constexpr auto hash1 = hashString("Hello, World!");
constexpr auto hash2 = hashString("Template Metaprogramming");
std::cout << "\n--- Compile-time String Hashing ---" << std::endl;
std::cout << "Hash of 'Hello, World!': " << hash1 << std::endl;
std::cout << "Hash of 'Template Metaprogramming': " << hash2 << std::endl;
// Compile-time array operations
constexpr auto squares = generateSquares<10>();
constexpr auto unsorted = std::array{5, 2, 8, 1, 9, 3};
constexpr auto sorted = bubbleSort(unsorted);
std::cout << "\n--- Compile-time Array Operations ---" << std::endl;
std::cout << "Squares of 0-9: ";
for (size_t i = 0; i < squares.size(); ++i) {
std::cout << squares[i];
if (i < squares.size() - 1) std::cout << ", ";
}
std::cout << std::endl;
std::cout << "Sorted array: ";
for (size_t i = 0; i < sorted.size(); ++i) {
std::cout << sorted[i];
if (i < sorted.size() - 1) std::cout << ", ";
}
std::cout << std::endl;
// if constexpr examples
std::cout << "\n--- if constexpr Examples ---" << std::endl;
constexpr auto intResult = processValue(10); // int
constexpr auto floatResult = processValue(3.14); // double
constexpr auto stringResult = processValue(std::string("Hello"));
std::cout << "Processed int (10): " << intResult << std::endl;
std::cout << "Processed float (3.14): " << floatResult << std::endl;
std::cout << "Processed string: " << stringResult << std::endl;
#if __cplusplus >= 202002L
// C++20 concepts
std::cout << "\n--- C++20 Concepts ---" << std::endl;
constexpr auto intPower = power(2, 10);
constexpr auto floatPower = power(2.0, 5);
std::cout << "2^10 (int): " << intPower << std::endl;
std::cout << "2.0^5 (double): " << floatPower << std::endl;
#endif
return 0;
}
SFINAE and Type Traits:
#include <iostream>
#include <type_traits>
#include <vector>
#include <string>
// ===== SFINAE EXAMPLES =====
// Enable function only for integral types
template<typename T>
typename std::enable_if<std::is_integral<T>::value, T>::type
safeAdd(T a, T b) {
std::cout << "Adding integers: ";
return a + b;
}
// Enable function only for floating point types
template<typename T>
typename std::enable_if<std::is_floating_point<T>::value, T>::type
safeAdd(T a, T b) {
std::cout << "Adding floating points: ";
return a + b;
}
// SFINAE to detect if a type has a specific member function
template<typename T>
class has_size_method {
private:
template<typename U>
static auto test(int) -> decltype(std::declval<U>().size(), std::true_type{});
template<typename>
static std::false_type test(...);
public:
static constexpr bool value = decltype(test<T>(0))::value;
};
// Function that behaves differently based on whether type has size() method
template<typename T>
auto getSize(const T& container) -> typename std::enable_if<has_size_method<T>::value, size_t>::type {
return container.size();
}
template<typename T>
auto getSize(const T&) -> typename std::enable_if<!has_size_method<T>::value, size_t>::type {
return 1; // Assume single element for types without size()
}
// ===== CUSTOM TYPE TRAITS =====
// Check if type is a container-like type
template<typename T>
struct is_container : std::false_type {};
template<typename T>
struct is_container<std::vector<T>> : std::true_type {};
template<>
struct is_container<std::string> : std::true_type {};
// Helper variable template (C++14)
template<typename T>
constexpr bool is_container_v = is_container<T>::value;
// ===== VARIADIC TEMPLATE METAPROGRAMMING =====
// Compile-time type checking for variadic templates
template<typename... Types>
struct all_integral;
template<>
struct all_integral<> : std::true_type {};
template<typename Head, typename... Tail>
struct all_integral<Head, Tail...> {
static constexpr bool value = std::is_integral<Head>::value && all_integral<Tail...>::value;
};
// Variadic sum that only works with integral types
template<typename... Args>
typename std::enable_if<all_integral<Args...>::value, long long>::type
variadicSum(Args... args) {
return (args + ...); // C++17 fold expression
}
// ===== TAG DISPATCHING =====
struct fast_tag {};
struct safe_tag {};
// Different implementations based on tag
template<typename Iterator>
void advanceImpl(Iterator& it, size_t n, fast_tag) {
std::cout << "Using fast advance (random access)" << std::endl;
it += n;
}
template<typename Iterator>
void advanceImpl(Iterator& it, size_t n, safe_tag) {
std::cout << "Using safe advance (incremental)" << std::endl;
for (size_t i = 0; i < n; ++i) {
++it;
}
}
template<typename Iterator>
void smartAdvance(Iterator& it, size_t n) {
using category = typename std::iterator_traits<Iterator>::iterator_category;
if constexpr (std::is_same_v<category, std::random_access_iterator_tag>) {
advanceImpl(it, n, fast_tag{});
} else {
advanceImpl(it, n, safe_tag{});
}
}
int main() {
std::cout << "=== SFINAE AND TYPE TRAITS ===" << std::endl;
// SFINAE function overloading
std::cout << "\n--- SFINAE Function Overloading ---" << std::endl;
std::cout << safeAdd(5, 3) << std::endl;
std::cout << safeAdd(3.14, 2.86) << std::endl;
// Has member detection
std::cout << "\n--- Member Function Detection ---" << std::endl;
std::vector<int> vec{1, 2, 3, 4, 5};
int number = 42;
std::cout << "Vector has size(): " << std::boolalpha << has_size_method<std::vector<int>>::value << std::endl;
std::cout << "Int has size(): " << std::boolalpha << has_size_method<int>::value << std::endl;
std::cout << "Size of vector: " << getSize(vec) << std::endl;
std::cout << "Size of int: " << getSize(number) << std::endl;
// Custom type traits
std::cout << "\n--- Custom Type Traits ---" << std::endl;
std::cout << "vector<int> is container: " << std::boolalpha << is_container_v<std::vector<int>> << std::endl;
std::cout << "string is container: " << std::boolalpha << is_container_v<std::string> << std::endl;
std::cout << "int is container: " << std::boolalpha << is_container_v<int> << std::endl;
// Variadic template metaprogramming
std::cout << "\n--- Variadic Template Constraints ---" << std::endl;
std::cout << "All integral check (int, long, char): " << std::boolalpha
<< all_integral<int, long, char>::value << std::endl;
std::cout << "All integral check (int, double, char): " << std::boolalpha
<< all_integral<int, double, char>::value << std::endl;
std::cout << "Sum of integers: " << variadicSum(1, 2, 3, 4, 5) << std::endl;
// variadicSum(1, 2.5, 3); // Would cause compilation error
// Tag dispatching
std::cout << "\n--- Tag Dispatching ---" << std::endl;
std::vector<int> numbers{1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
auto vecIt = numbers.begin();
smartAdvance(vecIt, 3);
std::cout << "Advanced to: " << *vecIt << std::endl;
return 0;
}
Metaprogramming Techniques Comparison:
Best Practices for Template Metaprogramming:
Evolution of Metaprogramming: C++ metaprogramming has evolved from complex template tricks to elegant, readable code. Modern C++ (C++11 and later) provides much cleaner alternatives to classical template metaprogramming techniques.
Performance Benefits: Template metaprogramming moves computations from runtime to compile-time, resulting in zero runtime overhead. Your programs run faster because the work is already done when the code is compiled!
Compilation Impact: While metaprogramming improves runtime performance, it can significantly increase compilation time. Use profiling tools to measure compilation impact and optimize accordingly.
Advanced Design Patterns
Design patterns are proven solutions to common programming problems. In modern C++, many patterns can be implemented more elegantly using templates, smart pointers, and lambda expressions.
Modern C++ Pattern Categories:
Creational
Object creation mechanisms
Structural
Object composition and relationships
Behavioral
Communication between objects
Modern C++
C++11/14/17/20 specific patterns
#include <iostream>
#include <memory>
#include <vector>
#include <functional>
#include <unordered_map>
#include <string>
#include <any>
#include <variant>
// ===== SINGLETON PATTERN (Modern C++11) =====
class ModernSingleton {
private:
ModernSingleton() = default;
public:
// Thread-safe singleton using static local variable
static ModernSingleton& getInstance() {
static ModernSingleton instance;
return instance;
}
// Delete copy constructor and assignment operator
ModernSingleton(const ModernSingleton&) = delete;
ModernSingleton& operator=(const ModernSingleton&) = delete;
void doSomething() {
std::cout << "Singleton instance doing work" << std::endl;
}
};
// ===== FACTORY PATTERN (Using Smart Pointers) =====
class Shape {
public:
virtual ~Shape() = default;
virtual void draw() const = 0;
virtual std::string getType() const = 0;
};
class Circle : public Shape {
public:
void draw() const override {
std::cout << "Drawing a Circle" << std::endl;
}
std::string getType() const override { return "Circle"; }
};
class Rectangle : public Shape {
public:
void draw() const override {
std::cout << "Drawing a Rectangle" << std::endl;
}
std::string getType() const override { return "Rectangle"; }
};
class ShapeFactory {
public:
static std::unique_ptr<Shape> createShape(const std::string& type) {
if (type == "circle") {
return std::make_unique<Circle>();
} else if (type == "rectangle") {
return std::make_unique<Rectangle>();
}
return nullptr;
}
};
// ===== OBSERVER PATTERN (Using Function Objects) =====
template<typename... Args>
class Signal {
private:
std::vector<std::function<void(Args...)>> slots;
public:
// Connect a slot (function/lambda) to the signal
void connect(std::function<void(Args...)> slot) {
slots.push_back(std::move(slot));
}
// Emit signal to all connected slots
void emit(Args... args) {
for (auto& slot : slots) {
slot(args...);
}
}
};
class Publisher {
private:
Signal<const std::string&>> messageSignal;
public:
void connectObserver(std::function<void(const std::string&)> observer) {
messageSignal.connect(std::move(observer));
}
void publishMessage(const std::string& message) {
std::cout << "Publishing: " << message << std::endl;
messageSignal.emit(message);
}
};
// ===== STRATEGY PATTERN (Using Lambdas) =====
class Calculator {
private:
std::function<double(double, double)> strategy;
public:
void setStrategy(std::function<double(double, double)> newStrategy) {
strategy = std::move(newStrategy);
}
double execute(double a, double b) {
return strategy ? strategy(a, b) : 0.0;
}
};
// ===== VISITOR PATTERN (Using std::variant) =====
struct AddOperation { double value; };
struct MultiplyOperation { double value; };
struct PrintOperation {};
using Operation = std::variant<AddOperation, MultiplyOperation, PrintOperation>;
class ModernCalculatorWithVisitor {
private:
double value = 0.0;
public:
void process(const Operation& op) {
std::visit([this](const auto& operation) {
using T = std::decay_t<decltype(operation)>;
if constexpr (std::is_same_v<T, AddOperation>) {
value += operation.value;
} else if constexpr (std::is_same_v<T, MultiplyOperation>) {
value *= operation.value;
} else if constexpr (std::is_same_v<T, PrintOperation>) {
std::cout << "Current value: " << value << std::endl;
}
}, op);
}
double getValue() const { return value; }
};
// ===== COMMAND PATTERN (Using Function Objects) =====
class CommandProcessor {
private:
std::vector<std::function<void()>> commands;
std::vector<std::function<void()>> undoCommands;
public:
void execute(std::function<void()> command, std::function<void()> undoCommand) {
command();
commands.push_back(std::move(command));
undoCommands.push_back(std::move(undoCommand));
}
void undo() {
if (!undoCommands.empty()) {
undoCommands.back()();
undoCommands.pop_back();
commands.pop_back();
}
}
};
// ===== PIMPL PATTERN (Pointer to Implementation) =====
class Widget {
private:
class Impl;
std::unique_ptr<Impl> pImpl;
public:
Widget();
~Widget();
Widget(const Widget& other);
Widget& operator=(const Widget& other);
Widget(Widget&& other) noexcept;
Widget& operator=(Widget&& other) noexcept;
void doSomething();
int getValue() const;
};
// Implementation would be in .cpp file
class Widget::Impl {
public:
int value = 42;
std::string data = "Hidden implementation";
void doWork() {
std::cout << "Widget implementation working with: " << data << std::endl;
}
};
Widget::Widget() : pImpl(std::make_unique<Impl>()) {}
Widget::~Widget() = default;
Widget::Widget(const Widget& other) : pImpl(std::make_unique<Impl>(*other.pImpl)) {}
Widget& Widget::operator=(const Widget& other) {
if (this != &other) {
*pImpl = *other.pImpl;
}
return *this;
}
Widget::Widget(Widget&& other) noexcept = default;
Widget& Widget::operator=(Widget&& other) noexcept = default;
void Widget::doSomething() { pImpl->doWork(); }
int Widget::getValue() const { return pImpl->value; }
// ===== CRTP PATTERN (Curiously Recurring Template Pattern) =====
template<typename Derived>
class Printable {
public:
void print() const {
static_cast<const Derived*>(this)->printImpl();
}
};
class Document : public Printable<Document> {
private:
std::string content;
public:
Document(const std::string& text) : content(text) {}
void printImpl() const {
std::cout << "Document: " << content << std::endl;
}
};
class Image : public Printable<Image> {
private:
std::string filename;
public:
Image(const std::string& file) : filename(file) {}
void printImpl() const {
std::cout << "Image: " << filename << std::endl;
}
};
int main() {
std::cout << "=== MODERN C++ DESIGN PATTERNS ===" << std::endl;
// Singleton Pattern
std::cout << "\n--- Singleton Pattern ---" << std::endl;
auto& singleton = ModernSingleton::getInstance();
singleton.doSomething();
// Factory Pattern
std::cout << "\n--- Factory Pattern ---" << std::endl;
auto circle = ShapeFactory::createShape("circle");
auto rectangle = ShapeFactory::createShape("rectangle");
if (circle) circle->draw();
if (rectangle) rectangle->draw();
// Observer Pattern
std::cout << "\n--- Observer Pattern ---" << std::endl;
Publisher publisher;
// Lambda observers
publisher.connectObserver([](const std::string& msg) {
std::cout << "Observer 1 received: " << msg << std::endl;
});
publisher.connectObserver([](const std::string& msg) {
std::cout << "Observer 2 logged: " << msg << std::endl;
});
publisher.publishMessage("Hello Observers!");
// Strategy Pattern
std::cout << "\n--- Strategy Pattern ---" << std::endl;
Calculator calc;
// Addition strategy
calc.setStrategy([](double a, double b) { return a + b; });
std::cout << "5 + 3 = " << calc.execute(5, 3) << std::endl;
// Multiplication strategy
calc.setStrategy([](double a, double b) { return a * b; });
std::cout << "5 * 3 = " << calc.execute(5, 3) << std::endl;
// Visitor Pattern with std::variant
std::cout << "\n--- Visitor Pattern (std::variant) ---" << std::endl;
ModernCalculatorWithVisitor calculator;
calculator.process(AddOperation{10.0});
calculator.process(MultiplyOperation{2.0});
calculator.process(PrintOperation{});
calculator.process(AddOperation{5.0});
calculator.process(PrintOperation{});
// Command Pattern
std::cout << "\n--- Command Pattern ---" << std::endl;
CommandProcessor processor;
int value = 0;
// Execute command with undo capability
processor.execute(
[&value]() { value += 10; std::cout << "Added 10, value: " << value << std::endl; },
[&value]() { value -= 10; std::cout << "Undid add 10, value: " << value << std::endl; }
);
processor.execute(
[&value]() { value *= 2; std::cout << "Multiplied by 2, value: " << value << std::endl; },
[&value]() { value /= 2; std::cout << "Undid multiply by 2, value: " << value << std::endl; }
);
processor.undo(); // Undo multiply
processor.undo(); // Undo add
// PIMPL Pattern
std::cout << "\n--- PIMPL Pattern ---" << std::endl;
Widget widget;
widget.doSomething();
std::cout << "Widget value: " << widget.getValue() << std::endl;
// CRTP Pattern
std::cout << "\n--- CRTP Pattern ---" << std::endl;
Document doc("Hello World");
Image img("photo.jpg");
doc.print();
img.print();
return 0;
}
Pattern Selection Guide:
Modern C++ Advantage: Many traditional design patterns are simplified or replaced by modern C++ features like lambdas, std::function, smart pointers, and templates, resulting in cleaner and more efficient code.
Performance Optimization
Performance optimization in C++ involves understanding the cost of operations, memory access patterns, compiler optimizations, and modern CPU architectures. Learn to write fast code without premature optimization.
Performance Optimization Principles:
📏 Measure First
Always profile before optimizing
🎯 Optimize Hotspots
Focus on code that runs most frequently
🧠 Cache-Friendly
Design for modern CPU cache hierarchies
⚡ Compile-Time
Move computations from runtime to compile-time
#include <iostream>
#include <vector>
#include <algorithm>
#include <chrono>
#include <numeric>
#include <random>
#include <memory>
#include <string>
class PerformanceTester {
public:
template<typename Func>
static double measureTime(Func&& func, const std::string& name) {
auto start = std::chrono::high_resolution_clock::now();
func();
auto end = std::chrono::high_resolution_clock::now();
auto duration = std::chrono::duration_cast<std::chrono::microseconds>(end - start);
double ms = duration.count() / 1000.0;
std::cout << name << ": " << ms << " ms" << std::endl;
return ms;
}
};
// ===== MEMORY ACCESS PATTERNS =====
class MemoryAccessDemo {
public:
static void cacheLocalityDemo() {
const size_t size = 10000000;
std::vector<int> data(size);
std::iota(data.begin(), data.end(), 0);
std::cout << "\n=== Cache Locality Demo ===" << std::endl;
// Sequential access (cache-friendly)
PerformanceTester::measureTime([&]() {
long long sum = 0;
for (size_t i = 0; i < size; ++i) {
sum += data[i];
}
volatile auto result = sum; // Prevent optimization
}, "Sequential access");
// Random access (cache-unfriendly)
std::vector<size_t> indices(size);
std::iota(indices.begin(), indices.end(), 0);
std::random_device rd;
std::mt19937 gen(rd());
std::shuffle(indices.begin(), indices.end(), gen);
PerformanceTester::measureTime([&]() {
long long sum = 0;
for (size_t i = 0; i < size / 100; ++i) { // Smaller subset for fairness
sum += data[indices[i]];
}
volatile auto result = sum;
}, "Random access (1/100 subset)");
}
static void memoryLayoutDemo() {
std::cout << "\n=== Memory Layout Demo ===" << std::endl;
const size_t count = 1000000;
// Array of Structures (AoS) - poor cache usage
struct PointAoS {
double x, y, z;
int id;
char padding[100]; // Simulate larger struct
};
std::vector<PointAoS> aos_points(count);
// Structure of Arrays (SoA) - better cache usage
struct PointsSoA {
std::vector<double> x, y, z;
std::vector<int> id;
PointsSoA(size_t size) : x(size), y(size), z(size), id(size) {}
};
PointsSoA soa_points(count);
// Initialize data
for (size_t i = 0; i < count; ++i) {
aos_points[i] = {double(i), double(i*2), double(i*3), int(i)};
soa_points.x[i] = double(i);
soa_points.y[i] = double(i*2);
soa_points.z[i] = double(i*3);
soa_points.id[i] = int(i);
}
// Test: sum only x coordinates
PerformanceTester::measureTime([&]() {
double sum = 0;
for (const auto& point : aos_points) {
sum += point.x;
}
volatile auto result = sum;
}, "AoS sum x coordinates");
PerformanceTester::measureTime([&]() {
double sum = 0;
for (double x : soa_points.x) {
sum += x;
}
volatile auto result = sum;
}, "SoA sum x coordinates");
}
};
// ===== ALGORITHMIC OPTIMIZATIONS =====
class AlgorithmicOptimizations {
public:
static void containerChoiceDemo() {
std::cout << "\n=== Container Choice Demo ===" << std::endl;
const size_t operations = 100000;
// Vector vs List for different operations
std::vector<int> vec;
std::list<int> lst;
// Random access (vector wins)
vec.resize(operations);
std::iota(vec.begin(), vec.end(), 0);
for (size_t i = 0; i < operations; ++i) {
lst.push_back(i);
}
PerformanceTester::measureTime([&]() {
long long sum = 0;
for (size_t i = 0; i < operations / 100; ++i) {
sum += vec[i * 100];
}
volatile auto result = sum;
}, "Vector random access");
// Insertion at beginning (list wins)
std::vector<int> vec2;
std::list<int> lst2;
PerformanceTester::measureTime([&]() {
for (size_t i = 0; i < operations / 1000; ++i) {
vec2.insert(vec2.begin(), i);
}
}, "Vector insert at beginning");
PerformanceTester::measureTime([&]() {
for (size_t i = 0; i < operations / 1000; ++i) {
lst2.push_front(i);
}
}, "List insert at beginning");
}
static void algorithmChoiceDemo() {
std::cout << "\n=== Algorithm Choice Demo ===" << std::endl;
const size_t size = 1000000;
std::vector<int> data(size);
std::iota(data.begin(), data.end(), 0);
std::random_device rd;
std::mt19937 gen(rd());
std::shuffle(data.begin(), data.end(), gen);
auto data_copy1 = data;
auto data_copy2 = data;
// Sorting comparison
PerformanceTester::measureTime([&]() {
std::sort(data_copy1.begin(), data_copy1.end());
}, "std::sort");
PerformanceTester::measureTime([&]() {
std::stable_sort(data_copy2.begin(), data_copy2.end());
}, "std::stable_sort");
// Search comparison (on sorted data)
std::sort(data.begin(), data.end());
int target = data[size / 2];
PerformanceTester::measureTime([&]() {
for (int i = 0; i < 1000; ++i) {
auto it = std::find(data.begin(), data.end(), target);
volatile auto result = it;
}
}, "Linear search (1000x)");
PerformanceTester::measureTime([&]() {
for (int i = 0; i < 1000; ++i) {
auto it = std::lower_bound(data.begin(), data.end(), target);
volatile auto result = it;
}
}, "Binary search (1000x)");
}
};
// ===== COMPILE-TIME OPTIMIZATIONS =====
class CompileTimeOptimizations {
public:
// Compile-time factorial
static constexpr long long factorial(int n) {
return (n <= 1) ? 1 : n * factorial(n - 1);
}
// Template metaprogramming for compile-time computation
template<int N>
struct PowerOfTwo {
static constexpr long long value = 2 * PowerOfTwo<N-1>::value;
};
template<>
struct PowerOfTwo<0> {
static constexpr long long value = 1;
};
static void constexprDemo() {
std::cout << "\n=== Compile-time vs Runtime Demo ===" << std::endl;
// Runtime computation
PerformanceTester::measureTime([&]() {
long long sum = 0;
for (int i = 0; i < 1000000; ++i) {
long long fact = 1;
for (int j = 1; j <= 10; ++j) {
fact *= j;
}
sum += fact;
}
volatile auto result = sum;
}, "Runtime factorial calculation");
// Compile-time computation
PerformanceTester::measureTime([&]() {
constexpr long long fact = factorial(10);
long long sum = 0;
for (int i = 0; i < 1000000; ++i) {
sum += fact;
}
volatile auto result = sum;
}, "Compile-time factorial (constexpr)");
std::cout << "Power of 2^10: " << PowerOfTwo<10>::value << " (computed at compile-time)" << std::endl;
}
};
// ===== MODERN C++ OPTIMIZATIONS =====
class ModernCppOptimizations {
public:
static void moveSemanticsBenefit() {
std::cout << "\n=== Move Semantics Benefit ===" << std::endl;
const size_t size = 1000000;
std::vector<std::string> strings;
// Prepare large strings
for (size_t i = 0; i < 1000; ++i) {
strings.emplace_back(size, 'A' + (i % 26));
}
// Copy semantics
PerformanceTester::measureTime([&]() {
std::vector<std::string> copied;
for (const auto& str : strings) {
copied.push_back(str); // Copy
}
volatile auto result = copied.size();
}, "Copy semantics");
// Move semantics (simulated)
PerformanceTester::measureTime([&]() {
std::vector<std::string> moved;
auto strings_copy = strings; // Work with copy
for (auto& str : strings_copy) {
moved.push_back(std::move(str)); // Move
}
volatile auto result = moved.size();
}, "Move semantics");
}
static void smartPointerOverhead() {
std::cout << "\n=== Smart Pointer Overhead ===" << std::endl;
const size_t operations = 10000000;
// Raw pointer
PerformanceTester::measureTime([&]() {
long long sum = 0;
for (size_t i = 0; i < operations; ++i) {
int* ptr = new int(i);
sum += *ptr;
delete ptr;
}
volatile auto result = sum;
}, "Raw pointer new/delete");
// unique_ptr
PerformanceTester::measureTime([&]() {
long long sum = 0;
for (size_t i = 0; i < operations; ++i) {
auto ptr = std::make_unique<int>(i);
sum += *ptr;
}
volatile auto result = sum;
}, "unique_ptr");
// shared_ptr
PerformanceTester::measureTime([&]() {
long long sum = 0;
for (size_t i = 0; i < operations; ++i) {
auto ptr = std::make_shared<int>(i);
sum += *ptr;
}
volatile auto result = sum;
}, "shared_ptr");
}
};
int main() {
std::cout << "=== C++ PERFORMANCE OPTIMIZATION DEMONSTRATION ===" << std::endl;
// Memory access patterns
MemoryAccessDemo::cacheLocalityDemo();
MemoryAccessDemo::memoryLayoutDemo();
// Algorithmic choices
AlgorithmicOptimizations::containerChoiceDemo();
AlgorithmicOptimizations::algorithmChoiceDemo();
// Compile-time optimizations
CompileTimeOptimizations::constexprDemo();
// Modern C++ features
ModernCppOptimizations::moveSemanticsBenefit();
ModernCppOptimizations::smartPointerOverhead();
std::cout << "\n=== Performance testing complete ===" << std::endl;
std::cout << "Note: Results may vary based on compiler, optimization flags, and hardware" << std::endl;
return 0;
}
Performance Optimization Checklist:
Memory
- Use stack allocation when possible
- Prefer std::array over C arrays
- Consider object pooling for frequent allocations
- Align data structures for cache efficiency
- Use Structure of Arrays for better cache locality
Algorithms
- Choose the right container for your use case
- Use STL algorithms instead of hand-written loops
- Consider parallel algorithms (C++17)
- Profile to find algorithmic bottlenecks
- Use Big O notation to guide choices
Compiler
- Use constexpr for compile-time computation
- Enable compiler optimizations (-O2, -O3)
- Use LTO (Link Time Optimization)
- Profile-guided optimization (PGO)
- Understand compiler auto-vectorization
Modern C++
- Use move semantics to avoid copies
- Prefer in-place construction (emplace)
- Use string_view to avoid string copies
- Consider std::optional vs exceptions
- Use structured bindings for readability
Premature Optimization Warning: "Premature optimization is the root of all evil" - Donald Knuth. Always profile first, optimize second. Focus on correctness and readability before micro-optimizations.
Profiling Tools: Use tools like Valgrind, Intel VTune, Google Benchmark, or built-in compiler profilers to measure actual performance before and after optimizations.
Advanced Best Practices
Master the advanced techniques and patterns that separate expert C++ developers from beginners. These practices ensure your code is efficient, maintainable, and follows modern C++ idioms.
Performance
- Use move semantics to avoid unnecessary copies
- Prefer stack allocation over heap when possible
- Use constexpr for compile-time computations
- Profile before optimizing
- Prefer algorithms over hand-written loops
Safety
- Always use RAII for resource management
- Prefer smart pointers over raw pointers
- Use const correctness everywhere
- Handle exceptions properly
- Avoid undefined behavior
Code Quality
- Follow the Rule of Zero/Three/Five
- Use meaningful names for everything
- Keep functions small and focused
- Prefer composition over inheritance
- Write self-documenting code
Modern C++
- Use auto for type deduction
- Prefer range-based for loops
- Use lambda expressions appropriately
- Leverage std::optional for nullable values
- Use structured bindings (C++17)
Congratulations! You've completed the Advanced C++ tutorial! You now have the knowledge to write high-performance, modern C++ code. Continue practicing with real projects and exploring the latest C++ standards to keep improving your skills.