Использование базового класса в качестве безопасного контейнера для указателей

Итак, я рассматривал этот вопрос Исключение выделения памяти в конструкторе, где мой босс заявляет в своем прекрасном ответе, что деструктор не будет вызван.

Что заставляет меня задуматься,

Если бы я написал

struct XBase
{
    int* a;
    char* b;
    float* c;

    XBase() : a(nullptr), b(nullptr), c(nullptr) {} 
    ~XBase()
    {
        delete[] a; delete[] b; delete[] c;
    }   
};

и

struct X : XBase
{
    X() {
        a = new int[100];
        b = new char[100];
        c = new float[100];
    }
}

Затем, если выделение c завершается с ошибкой (при исключении выбрасывается), тогда будет вызван деструктор XBase, так как был создан базовый класс.

И нет утечки памяти?

Правильно ли я?

Ответ 1

Вы правы; это будет работать, потому что:

  • К тому времени, когда выполняется тело конструктора X, XBase уже построено, и его деструктор будет вызываться.
  • Выполнение delete или delete[] в нулевых указателях абсолютно корректно и ничего не делает.

Итак, если сбой a, b или c не выполняется, деструктор XBase освободит все.

Но, очевидно, этот дизайн заставляет вас писать гораздо больше кода, который вам нужен, поскольку вы можете просто использовать std::vector или std::unique_ptr<T[]>.

Ответ 2

Просто, чтобы иметь официальную версию для резервного копирования претензий,

& раздел; 15.2/2

Объект любой продолжительности хранения, инициализация или уничтожение которой завершается с помощью исключения, будет иметь деструкторы, выполняемые для всех его полностью построенных подобъектов [...].

Ответ 3

Если вы беспокоитесь о сбое в распределении памяти, я бы поместил его в отдельный метод, например Build или Create. Затем вы можете позволить деструктору обработать его, если он был инициализирован (но определенно проверьте указатель перед слепое delete -ing.

Другим решением являются интеллектуальные указатели, которые не предназначены для этого случая обязательно, но автоматически освобождают их содержимое.