Как С++ освобождает память, когда конструктор генерирует исключение, и используется пользовательское новое

Я вижу следующие конструкции:

  • new X освободит память, если конструктор X выбрал.

  • operator new() может быть перегружен.

Каноническое определение новой перегрузки оператора void *operator new(size_t c, heap h) и соответствующее operator delete.

Наиболее распространенным оператором новой перегрузки является размещение new, которое void *operator new(void *p) { return p; }

Вы почти всегда не можете называть delete указателем, заданным для размещения new.

Это приводит к одному вопросу: как очищается память при срабатывании конструктора X и используется перегруженный new?

Ответ 1

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

Ответ 2

В принципе, если нет оператора delete, который соответствует оператору new, то ничего не делается. Ничего не сделано и в случае размещения нового, потому что соответствующий оператор удаления места размещения является no-op. Исключение не переадресовано: оно продолжает свой курс, поэтому вызывающий абонент имеет возможность (и ответственность) освобождать выделенную память.

Размещение new называется так, потому что оно используется для размещения объекта в памяти в противном случае; поскольку память не была приобретена новым оператором, маловероятно, что он может быть освобожден оператором удаления. На практике вопрос спорный, потому что (поскольку С++ 03, по крайней мере), не разрешается заменять новый оператор размещения (который имеет прототип operator new(size_t, void*) или delete (operator delete(void*, void*)). Оператор нового поставленного места размещения возвращает его второй аргумент, а поставленный оператор удаления места размещения - нет-op.

Другие операторы new и delete могут быть заменены либо глобально, либо для определенного класса. Если вызывается пользовательский оператор new, а конструктор генерирует исключение, и существует соответствующий оператор delete, то этот оператор удаления будет вызываться для очистки до того, как будет распространено исключение. Однако это не ошибка, если нет соответствующего оператора delete.

Ответ 3

Во-первых, пример:

#include <cstddef>
#include <iostream>

struct S
{
    S(int i) { if(i > 42) throw "up"; }

    static void* operator new(std::size_t s, int i, double d, char c)
    {
        std::cout << "allocated with arguments: "
                  <<i<<", "<<d<<", "<<c<<std::endl;
        return new char[s];
    }

    static void operator delete(void* p, int i, double d, char c)
    {
        std::cout << "deallocated with arguments: "
                  <<i<<", "<<d<<", "<<c<<std::endl;
        delete[] (char*)p;
    }

    static void operator delete(void* p)
    {
        std::cout << "deallocated w/o arguments"<<std::endl;
        delete[] (char*)p;
    }
};

int main()
{
    auto p0 = new(1, 2.0, '3') S(42);

    S* p1 = nullptr;
    try
    {
        p1 = new(4, 5.0, '6') S(43);
    }catch(const char* msg)
    {
        std::cout << "exception: "<<msg<<std::endl;
    }

    delete p1;
    delete p0;
}

Вывод:

allocated with arguments: 1, 2, 3
allocated with arguments: 4, 5, 6
deallocated with arguments: 4, 5, 6
exception: up
deallocated w/o arguments

Каноническое определение новой перегрузки оператора void *operator new(std::size_t, heap h)

Я не вижу, как это канонически, так как это не разрешено: Итак, теперь это действительная форма размещения new:)

[basic.stc.dynamic.allocation]/1

Функция распределения должна быть функцией-членом класса или глобальной функцией; программа плохо сформирована, если функция распределения объявлена ​​в области пространства имен, отличной от глобальной области действия, или объявлена ​​статической в ​​глобальной области. Тип возврата должен быть void*. Первый параметр должен иметь тип std::size_t. Первый параметр не должен иметь связанного аргумента по умолчанию. Значение первого параметра должно интерпретироваться как запрошенный размер выделения.

[акцент мой]

Вы можете перегрузить функцию распределения, которая будет вызываться для формы размещения new, см. [expr.new] (она явно не разрешена в [basic.stc.dynamic.allocation] для функций без шаблона, но также не запрещено). Размещение, указанное в new(placement), обобщается здесь на список выражений. Каждое выражение в списке выражений для конкретного нового выражения передается в качестве дополнительных аргументов функции распределения. Если вызывается функция освобождения (например, поскольку вызываемый ctor выдает исключение), те же аргументы плюс ведущий void* (возвращаемое значение функции распределения) передаются функции дезактивации.

[expr.new]/18 состояний:

Если какая-либо часть инициализации объекта, описанная выше, завершается сбросом исключения, для объекта было получено хранилище, и может быть найдена подходящая функция освобождения, функция освобождения вызывается для освобождения памяти, в которой находился объект после чего исключение продолжает распространяться в контексте нового выражения. Если не удается найти однозначную функцию дезадаптации, распространение этого исключения не приведет к освобождению памяти объектов. [Примечание: Это удобно, если вызываемая функция выделения не выделяет память; в противном случае это может привести к утечке памяти. - конечная нота]

и/21

Если новое выражение вызывает функцию освобождения, оно передает значение, возвращаемое из вызова функции распределения, в качестве первого аргумента типа void*. Если вызывается функция освобождения места размещения, ему передаются те же дополнительные аргументы, которые были переданы функции распределения места размещения, то есть те же аргументы, что и те, которые указаны в синтаксисе нового места размещения.

и/20

Объявление функции освобождения места размещения соответствует объявлению функции распределения места размещения, если оно имеет одинаковое количество параметров и после преобразований параметров все типы параметров, кроме первого, идентичны. Любая функция освобождения места размещения соответствует функции распределения места размещения. Если поиск находит одну подходящую функцию освобождения, эта функция будет вызвана; иначе не будет вызываться функция освобождения. Если поиск находит двухпараметрическую форму обычной функции освобождения, и эта функция, рассматриваемая как функция освобождения места размещения, была бы выбрана в качестве соответствия для функции распределения, программа плохо сформирована. [Пример:

struct S {
    // Placement allocation function:
    static void* operator new(std::size_t, std::size_t);
    // Usual (non-placement) deallocation function:
    static void operator delete(void*, std::size_t);
};

S* p = new (0) S; // ill-formed: non-placement deallocation function matches
                  // placement allocation function

- конец примера]

Возвращаясь к [basic.stc.dynamic.deallocation]:

1 Функции освобождения должны быть функциями класса или глобальными функциями; программа плохо сформирована, если функции освобождения объявлены в области пространства имен, отличной от глобальной области или объявленной статикой в ​​глобальной области.

2 Каждая функция освобождения должна возвращать void, а ее первый параметр должен быть void*. Функция дезадаптации может иметь более одного параметра.

Ответ 4

"размещение нового" не является перегруженной версией нового, но является одним из вариантов нового оператора, а также не может быть перегружен.

Смотрите здесь список новых операторов здесь, а также описание того, как они перегружаются.

Если конструктор создает исключение при использовании места размещения new, компилятор знает, какой новый оператор был использован, и удаление места размещения.

Ответ 5

Когда конструкция объекта, создаваемого как часть нового выражения, не выполняется, будет вызвана соответствующая функция освобождения - если она есть -. Например,

new X;

будет использовать следующую пару функций распределения/освобождения.

void * operator new(std::size_t);
void operator delete(void *);

Аналогично, для размещения нового вида

new(&a) X;

будут использоваться версии размещения operator new и operator delete.

void * operator new(std::size_t, void *);
void operator delete(void *, void *);

Обратите внимание, что последняя функция намеренно не выполняет никаких действий.