Chapter 22: Function Pointers, Dynamic Memory, Lambdas, and Ellipsis

Dynamic Memory Allocation

Dynamic memory allocation allows you to request memory from the heap instead of the stack.

Basic Usage

int* ptr{ new int{4} }; // dynamically allocate an integer and assign the address to ptr
delete ptr; // return the memory pointed to by ptr to the operating system
ptr = nullptr; // set ptr to be a null pointer

Handling Allocation Failure

int* value { new (std::nothrow) int }; // value will be set to a null pointer if the allocation fails
if (!value) // handle case where new returned null
{
    // Handle allocation failure
}

Memory Leak

A memory leak occurs when dynamically allocated memory is not properly deallocated.

void doSomething() { int* ptr{ new int{} }; } // pointer goes out of scope without delete

int value{ 5 };
int* ptr{ new int{} }; // allocate memory
delete ptr; // delete the memory
ptr = &value; // reassign pointer to address of value

Deleting Arrays

int* array{ new int[length]{} }; // dynamically allocate an array
delete[] array; // deallocate the array

Destructors

Destructors are special member functions that are called when an object is destroyed.

class IntArray
{
private:
    int* m_array;

public:
    ~IntArray() // destructor
    {
        delete[] m_array; // dynamically delete the array we allocated earlier
    }
};

Pointers to Pointers

int value{ 5 };
int* ptr{ &value };
int** ptrptr { &ptr };
std::cout << **ptrptr << '\n'; // dereference to get pointer to int, dereference again to get int value

Two-Dimensional Dynamically Allocated Arrays

int** array { new int*[10] }; // allocate an array of 10 int pointers — these are our rows
for (int count { 0 }; count < 10; ++count)
// deleting
for (int count { 0 }; count < 10; ++count)
    delete[] array[count];
delete[] array; // this needs to be done last

Void Pointers (Generic Pointers)

void* ptr {}; // ptr is a void pointer
int* intPtr{ static_cast<int*>(voidPtr) }; // cast void pointer to an int pointer

Function Pointers

Function pointers are pointers that point to functions.

Basic Usage

int foo() { return 5; }

int main()
{
    auto fcnPtr{ &foo };
    (*fcnPtr)(); // call function foo() through fcnPtr. explicit dereference
    fcnPtr();    // call function foo() through fcnPtr. implicit dereference
    return 0;
}

Passing Functions as Arguments (Callback Functions)

void sort(bool (*comparisonfnc)(int, int)) {}

bool ascending(int a, int b) { return a < b; }

sort(ascending); // pass function as argument

Using std::function

#include <functional>

using ValidateFunction = std::function<bool(int, int)>;

bool validate(ValidateFunction fcn);

Command Line Arguments

#include <sstream>

int main(int argc, char* argv[])
{
    std::stringstream convert{ argv[1] }; // set up a stringstream variable named convert, initialized with the input from argv[1]
    int myint{};
    if (!(convert >> myint)) // do the conversion
        myint = 0; // if conversion fails, set myint to a default value
}

Ellipsis (Avoid)

Ellipsis allows functions to accept a variable number of arguments.

#include <cstdarg> // needed to use ellipsis

double findAverage(int count, ...)
{
    int sum{ 0 };
    std::va_list list; // declare a va_list

    va_start(list, count); // initialize the va_list

    for (int arg{ 0 }; arg < count; ++arg)
    {
        sum += va_arg(list, int); // get values out of the ellipsis
    }

    va_end(list); // cleanup the va_list

    return static_cast<double>(sum) / count;
}

Lambdas (Anonymous Functions/Functors)

Lambdas are anonymous functions that can be defined within other functions.

Basic Usage

[ captureClause ] ( parameters ) -> returnType
{
    statements;
}

Example

auto found{ std::find_if(arr.begin(), arr.end(),
                         [](std::string_view str) // here's our lambda, no capture clause
                         {
                           return str.find("nut") != std::string_view::npos;
                         }) };

Capturing Variables

int x = 5;
auto lambda = [x]() mutable { x += 2; }; // capture x by value, mutable to modify
lambda();

auto refLambda = [&x]() { x += 2; }; // capture x by reference
refLambda();

auto allByValue = [=]() {};  // auto copy capture all needed variables
auto allByRef = [&]() {};    // auto reference capture all needed variables

Using std::function

void repeat1(const std::function<void(int)>& fn); // Case 1: use a std::function parameter

template <typename T>
void repeat2(const T& fn); // Case 2: use a function template with a type template parameter

void repeat3(const auto& fn); // Case 3: use the abbreviated function template syntax (C++20)

void repeat4(void (*fn)(int)); // Case 4: use function pointer (only for lambda with no captures)

Memory Segments

The memory that a program uses is typically divided into a few different areas, called segments:

Heap

Stack