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

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

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

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

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

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

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

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


person Joshua    schedule 30.10.2013    source источник
comment
Какой перегрузил new? Размещение-новый сорт?   -  person John Dibling    schedule 30.10.2013
comment
Следует обратить внимание на то, что стандарт C++ (по крайней мере, C++03) не позволяет программе перегружать новое размещение.   -  person John Dibling    schedule 30.10.2013
comment
@Джон Диблинг: иди прочитай new.h   -  person Joshua    schedule 30.10.2013
comment
Я не уверен, что понимаю вашу точку зрения? Этот файл либо является реализацией стандартной библиотеки C++, либо не соответствует требованиям.   -  person John Dibling    schedule 30.10.2013
comment
Дело в том, что компилятор по определению должен быть в состоянии скомпилировать части стандартной библиотеки, написанные на C++, и по расширению, если вы не ссылаетесь на него, вы можете заменить его.   -  person Joshua    schedule 30.10.2013
comment
Так вы говорите о реализации собственной стандартной библиотеки C++?   -  person John Dibling    schedule 30.10.2013


Ответы (5)


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

person stonemetal    schedule 30.10.2013
comment
Это неправильное прочтение страницы Википедии, которая, в свою очередь, содержит некоторые неточности. Пример переопределенной пары new/delete на странице Википедии — это всего лишь пример; вы можете использовать любую последовательность дополнительных типов аргументов (кроме void*, которая зарезервирована). Я подозреваю, что цель примера из Википедии заключалась в том, что тип A будет своего рода распределителем. В любом случае, вы можете вызвать ::delete(void*, void*) по умолчанию, если хотите, но это ничего не сделает. - person rici; 30.10.2013

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

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

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

person rici    schedule 30.10.2013

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

#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*. Функция освобождения может иметь более одного параметра.

person dyp    schedule 30.10.2013

'placement new' — это не перегруженная версия оператора new, а один из вариантов оператора new, к тому же такой, который не может быть перегружен.

Посмотреть список новых операторов можно здесь вместе с описанием как работает их перегрузка.

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

person Mihai Stan    schedule 30.10.2013

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

new X;

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

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

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

new(&a) X;

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

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

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

person avakar    schedule 30.10.2013