Dealing with dynamically allocated memory for buffers in C++

Content

Our goal will be to implement a type DoubleArray with the following features:

Before we discuss the implementation we will motivate the usefulness of such a type.

Motivation

For optimizing the GEMM operation we typically will need some buffers. These buffers will be used to store blocks of some matrices. Usually the size of the buffers will be too large for keeping them on the stack. So we have two options:

We will see that using buffers on the data segment makes it harder to extend our (so far) single-threaded GEMM operation to a multi-threaded variant. So basically we will have often the following pattern:

void
some_important_function(/* arguments*/)
{
    double *buffer = new double[BUFFER_SIZE];

    /* check if memory allocation was successful */

    /* code using the buffer */

    delete [] buffer;
}

There are at least two issues we always have to be aware of:

Exercise

Write a C++ class DoubleArray that wraps a double pointer and has the following properties:

For teaching purposes, we also want the following debug information:

Testing features

Basically implement class DoubleArray such that the following code works:

#include <cstddef>
#include <printf.hpp>

namespace tools {

/* TODO: Your code for class DoubleArray */

} // namespace tools

void
printArray(std::size_t n, const double *x)
{
    for (std::size_t i=0; i<n; ++i) {
        fmt::printf("%10.3lf", x[i]);
    }
    fmt::printf("\n");
}

int
main()
{
    // Allocate a buffer with length 10 on heap
    tools::DoubleArray   p(10);

    // Compare size of DoubleArray and double*
    fmt::printf("sizeof(DoubleArray) = %zu\n", sizeof(tools::DoubleArray));
    fmt::printf("sizeof(double*)     = %zu\n", sizeof(double*));
    assert(sizeof(double*)==sizeof(tools::DoubleArray));

    for (std::size_t i=0; i<10; ++i) {
        p[i] = i;
    }

    // Getting a raw pointer by taking the address of an array element
    printArray(10, &p[0]);

    fmt::printf("main(): Hope somehow all my trash gets removed\n");
}

So the output produced by this could look like this:

theon$ g++ -Wall -std=c++11 -o ex1 ex1.cpp
theon$ ex1
DoubleArray(): array allocated at 0x61b5f0
sizeof(DoubleArray) = 8
sizeof(double*)     = 8
     0.000     1.000     2.000     3.000     4.000     5.000     6.000     7.000     8.000     9.000
main(): Hope somehow all my trash gets removed
~DoubleArray(): delete [] 0x61b5f0
theon$ 

Tests that must not compile (but unfortunately will)

Unfortunately the following test will compile. Even worse, when you run it the same memory block gets release twice!

#include <cstddef>
#include <printf.hpp>

namespace tools {

/* TODO: Your code for class DoubleArray */

} // namespace tools

int
main()
{
    // Allocate a buffer with length 10 on heap
    tools::DoubleArray   p(10);
    tools::DoubleArray   q = p;

    fmt::printf("main(): Hope somehow all my trash gets removed\n");
}

Make sure you understand why the following happens (and why this is a bad thing):

theon$ g++ -Wall -std=c++11 -o ex2 ex2.cpp
theon$ ex2
DoubleArray(): array allocated at 0x61a330
main(): Hope somehow all my trash gets removed
~DoubleArray(): delete [] 0x61a330
~DoubleArray(): delete [] 0x61a330
theon$ 

Tests that must not compile (and won't)

In our definition an object of type tools::DoubleArray is a wrapper for a pointer that always points to the same address. So assigning objects of this type to each other is illegal. So the following code should not compile:

#include <cstddef>
#include <printf.hpp>

namespace tools {

/* TODO: Your code for class DoubleArray */

} // namespace tools

int
main()
{
    // Allocate a buffer with length 10 on heap
    tools::DoubleArray   p(10);
    tools::DoubleArray   q(10);

    q = p;

    fmt::printf("main(): Hope somehow all my trash gets removed\n");
}

Exercise