No history yet

Advanced C++ Programming

Beyond the Basics

You already know that C++ lets you create variables, control program flow with loops, and organize code into functions. Now, we'll build on that foundation, starting with two concepts that give C++ its power and reputation for performance: pointers and references.

A reference is an alias for an existing variable. Once you initialize a reference, it's permanently tied to that original variable. A pointer, on the other hand, is a variable that stores a memory address. It can be changed to point to different variables or to nothing at all.

#include <iostream>

void changeValue(int& ref) { // Pass by reference
    ref = 20;
}

void changeValueWithPtr(int* ptr) { // Pass by pointer
    if (ptr != nullptr) {
        *ptr = 30; // Dereference to change the value
    }
}

int main() {
    int num = 10;
    int& numRef = num; // numRef is an alias for num

    changeValue(num); // Pass the variable directly
    std::cout << "After changeValue: " << num << std::endl; // Prints 20

    int* numPtr = &num; // numPtr holds the address of num
    changeValueWithPtr(numPtr); // Pass the pointer
    std::cout << "After changeValueWithPtr: " << num << std::endl; // Prints 30

    return 0;
}

Think of a reference as a nickname for a variable, while a pointer is like writing down the variable's home address. Both can be used to access the original, but the address can be erased and a new one written down.

Object-Oriented Programming

Object-Oriented Programming (OOP) allows you to model real-world concepts using classes and objects. It's built on three core principles: encapsulation, inheritance, and polymorphism.

Encapsulation

noun

The bundling of data (attributes) and the methods (functions) that operate on that data into a single unit called a class. It also involves restricting direct access to some of an object's components, which is a key part of data hiding.

In C++, we use access specifiers like public, private, and protected to enforce encapsulation. Public members are accessible from anywhere, while private members can only be accessed by the class's own functions. This prevents accidental modification and keeps the class's internal state consistent.

class BankAccount {
private:
    double balance;

public:
    BankAccount(double initialBalance) : balance(initialBalance) {}

    void deposit(double amount) {
        if (amount > 0) {
            balance += amount;
        }
    }

    double getBalance() const {
        return balance;
    }
};

Inheritance allows a new class (derived class) to acquire the properties and behaviors of an existing class (base class). This promotes code reuse and creates a logical hierarchy. For instance, Car and Truck could both inherit from a base Vehicle class.

Polymorphism, which means "many forms," allows objects of different classes to be treated as objects of a common base class. The most common way this is achieved in C++ is through virtual functions. When you call a virtual function through a base class pointer, the program decides at runtime which version of the function to execute based on the object's actual type. This is called dynamic dispatch.

#include <iostream>

class Shape { // Base class
public:
    virtual void draw() const {
        std::cout << "Drawing a generic shape.\n";
    }
};

class Circle : public Shape { // Derived class
public:
    void draw() const override { // Override the base class function
        std::cout << "Drawing a circle.\n";
    }
};

class Square : public Shape { // Another derived class
public:
    void draw() const override {
        std::cout << "Drawing a square.\n";
    }
};

void render(const Shape& s) {
    s.draw(); // Which draw() is called?
}

int main() {
    Circle c;
    Square sq;
    render(c);  // Calls Circle::draw()
    render(sq); // Calls Square::draw()
    return 0;
}

The Standard Library

The C++ Standard Template Library (STL) is a powerful set of tools that provides pre-built data structures and algorithms. Using the STL saves time and helps you write more reliable code. Its three main components are containers, algorithms, and iterators.

Containers are objects that store data. Algorithms are procedures that act on the data. Iterators are the glue that connects them, providing a way for algorithms to traverse the elements within containers.

ContainerTypeKey Feature
std::vectorSequenceDynamic array; fast random access.
std::listSequenceDoubly-linked list; fast insertion/deletion anywhere.
std::mapAssociativeStores key-value pairs; sorted by key.
std::unordered_mapAssociativeKey-value pairs; uses hash table for fast lookups.
std::setAssociativeStores unique elements; sorted.

The choice of container depends on your needs. If you need to access elements by index frequently, a vector is a great choice. If you'll be adding and removing elements from the middle of a large collection, a list might be better. For fast lookups by a key, unordered_map is often the top performer.

Memory Management

Understanding how C++ handles memory is crucial. There are two main places memory is allocated: the stack and the heap.

  • The Stack: This is where local variables and function calls are stored. It's fast and memory is managed automatically. When a function ends, its variables are popped off the stack.
  • The Heap: This is a large pool of memory available for the program to use. You must manually request memory from the heap (using new) and release it when you're done (using delete).

Manual memory management is error-prone. Forgetting to call delete leads to memory leaks. Using memory after it has been deleted causes undefined behavior. To solve this, modern C++ introduced smart pointers.

Lesson image

Smart pointers are objects that act like pointers but automatically manage the memory they point to. When the smart pointer goes out of scope, it automatically calls delete on the memory it owns.

Smart PointerOwnership SemanticsBest Use Case
std::unique_ptrExclusive, unique ownership.When you want one pointer to own a resource. It cannot be copied.
std::shared_ptrShared ownership.When multiple pointers need to co-own a resource. It keeps a reference count.
std::weak_ptrNon-owning reference.To observe a resource managed by a shared_ptr without affecting its lifetime. Breaks circular references.
#include <iostream>
#include <memory> // Required for smart pointers

class Widget {
public:
    Widget() { std::cout << "Widget created\n"; }
    ~Widget() { std::cout << "Widget destroyed\n"; }
};

void useWidget() {
    // Memory is allocated on the heap for a Widget object.
    std::unique_ptr<Widget> w_ptr = std::make_unique<Widget>();
    
    // ... use the widget ...

} // w_ptr goes out of scope here, and the Widget is automatically destroyed.

int main() {
    useWidget();
    std::cout << "Back in main\n";
    return 0;
}

This pattern of resource management, where an object's lifetime is tied to the scope of a variable, is called Resource Acquisition Is Initialization (RAII). It's a cornerstone of modern, safe C++ programming.

Concurrency and Modern Features

As processors gain more cores, writing concurrent code that can perform multiple tasks at once has become essential. C++ provides tools to manage this complexity, primarily through threads and synchronization primitives.

The std::thread class allows you to launch a new thread of execution. To prevent chaos when multiple threads access the same data, you use tools like std::mutex (mutual exclusion) to ensure only one thread can access a resource at a time, and std::atomic for simple, lock-free operations.

#include <iostream>
#include <thread>
#include <vector>

void worker_function() {
    std::cout << "Hello from thread!\n";
}

int main() {
    std::vector<std::thread> threads;
    for (int i = 0; i < 5; ++i) {
        threads.emplace_back(worker_function);
    }

    std::cout << "Hello from main!\n";

    for (auto& t : threads) {
        t.join(); // Wait for each thread to finish
    }

    return 0;
}

Beyond concurrency, C++ continues to evolve. More recent standards (C++14, C++17, C++20) have added features like lambda expressions, the auto keyword for type inference, and move semantics, which allow for efficient transfer of resources from one object to another instead of expensive copying.

These advanced tools enable you to write C++ code that is not only fast but also safe, maintainable, and highly expressive.

Quiz Questions 1/5

What is a fundamental difference between a pointer and a reference in C++?

Quiz Questions 2/5

Which Object-Oriented Programming principle is primarily enforced by using public and private access specifiers?

That's a quick tour of some advanced C++ concepts. Each of these topics is deep, but now you have a map of the territory beyond the fundamentals.