Задача «Обнаружение утечек памяти»

Задание

Реализовать обнаружение утечек памяти с C++ программах.

Описание

Простейший способ обнаружения утечки памяти с помощью функции _DumpMemoryLeaks в Visual Studio.

// main.cpp
void main(void) {
    // код
    _CrtDumpMemoryLeaks(); // здесь вся выделенная память должна быть освобождена
}

Но данный способ не работает, если существуют сложные глобальные и статические объекты.

// main.cpp

class A {
    char * p;
public:
    A(void) : p(new char[1024]) {}
    ~A(void) { delete[] p; }
};

static A staticA;

void fn(void) {
    static A localstaticA;
}

void main(void) {
    fn();
    _CrtDumpMemoryLeaks(); // глобальные объекты не уничтожены, их деструкторы не вызваны
}

Кроме того, глобальные статические объекты могут быть объявлены и созданы в другом модуле.

// another.cpp

class B {
    char * p;
public:
    B(void) : p(new char[256]) {}
    ~B(void) { delete[] p; }
};

static B staticB;

void g(void) {
    static B localstaticB;
}

Первое приближение

Чтобы корректно детектировать утечки памяти, необходимо создать глобальный объект, который в деструкторе будет вызывать _CrtDumpMemoryLeaks().

class MemoryDump {
public:
    MemoryDump(void) { }
    ~MemoryDump(void) { _CrtDumpMemoryLeaks(); }
};

Объект класса MemoryDump должен быть создан в самом начале cpp файла, так чтобы он, среди других глобальных и статических объектов всегда создавался первым. Тогда при деинициализации гарантируется обратный порядок вызова деструкторов, и поэтому _CrtDumpMemoryLeaks() в деструкторе будет вызвана уже после вызова деструкторов у всех остальных глобальных объектов.

Второе приближение

Если в проекте присутствует несколько cpp файлов, то потребуется создать несколько таких глобальных объектов (т.к. порядок инициализации / деинициализации глобальных объектов в разных модулях не определён и может быть любым). Но при этом требуется чтобы _CrtDumpMemoryLeaks() вызывалась только в последнем вызове деструктора.

Для этого потребуется преобразовать наш класс в соответствии с паттерном одиночка: создание и уничтожение объекта происходит множество раз, но реально существует всегда только один экземпляр объекта, который один раз создаётся (первый вызов конструктора) и только один раз уничтожается (последний вызов деструктора).

Результат

Необходимо оформить класс и всю необходимую логику в виде отдельного модуля, а также сформулировать дополнительные правила использования этого модуля (если требуется).

Приведённый выше код можно использовать в качестве тестовых примеров для проверки работы модуля.