Это реализация нового оператора в libstdc++:
_GLIBCXX_WEAK_DEFINITION void *
operator new (std::size_t sz) _GLIBCXX_THROW (std::bad_alloc)
{
void *p;
/* malloc (0) is unpredictable; avoid it. */
if (__builtin_expect (sz == 0, false))
sz = 1;
while ((p = malloc (sz)) == 0)
{
new_handler handler = std::get_new_handler ();
if (! handler)
_GLIBCXX_THROW_OR_ABORT(bad_alloc());
handler ();
}
return p;
}
Кто гарантирует, что исключение в конструкторе освободит выделенную память?
Upd: спасибо комментаторам - на самом деле я имел в виду исключительную безопасность нового выражения.
какой конструктор? новый оператор просто выделяет память, он не создает объекты.
Какой конструктор?
@bolov Оператор new наверняка вызывает конструктор (за исключением формы new T
для тривиальных типов). Функция operator new
не работает. Да, терминология C++ сбивает с толку.
Таким образом, было бы лучше не использовать термин «новый оператор»; действительно, сам стандарт делает это лишь мимоходом, нечасто и таким образом, что лично я считаю недостатком. new
— это ключевое слово, а выражение new T
— это новое выражение.
У нас есть «оператор новый» и «новое выражение». «Новые выражения» вызывают «оператор новый» (кроме нового размещения) для выделения памяти, а затем создают объект (объекты). Я имел в виду оператор new (который представлен в вопросе и не создает объекты). Действительно запутанно.
Хорошо, возможно, я говорю о «новом выражении», а не о новом операторе. Как и где в коде реализована исключительная безопасность нового выражения?
@bolov «новое выражение» = «новый оператор». Я согласен с астероидами в том, что это сбивающий с толку термин, и его лучше избегать. Но он широко используется даже среди экспертов по C++, в том числе по стандарту C++.
Вы смешиваете в своем вопросе «новое выражение» и «новый оператор».
Новое выражение выглядит так: A* a = new A();
Язык C++ определяет, что это выражение оценивается примерно так (упрощенный псевдокод):
void* __a = operator new(sizeof(A));
try {
A::A(this = static_cast<A*>(__a));
} catch (...) { operator delete (__a); throw; }
Как видите, память освобождается, если происходит исключение.
Более подробное объяснение:
Если инициализация завершается выдачей исключения (например, из конструктор), если new-expression выделил какую-либо память, он вызывает соответствующая функция освобождения: оператор удаления для типа, отличного от массива, оператор delete[] для типа массива.
Реальный код, сгенерированный компилятором, смотрите здесь. Вы можете заметить вызов оператора delete, даже если исходный код его не содержит:
struct A { A(); };
A::A() { throw 13; }
int main()
{
auto a = new A();
}
Ассемблер:
call 401040 <operator new(unsigned long)@plt>
...
call 401172 <A::A()>
...
call 401050 <operator delete(void*, unsigned long)@plt>
...
call 401080 <_Unwind_Resume@plt>
Но где код, реализующий такую логику?
Генерируется компилятором
@ChrisUzdavinis, чтобы мы могли сказать, что любое новое выражение на самом деле заключено в блок try-catch? Или в этом случае компилятор делает это по-другому? Я понимаю, что это определяется реализацией, но давайте поговорим о самых популярных компиляторах, таких как gcc или clang?
@MaxDmitrichenko - я добавил ссылку на пример, сгенерированный из CompilerExplorer. Как именно он генерируется - зависит от поставщика компилятора. Не забывайте - этот код в первой части моего ответа является псевдокодом - не относитесь к нему слишком серьезно - это просто иллюстрация, а не реальный код.
Не могли бы вы привести пример? Откуда выбрасывается исключение — из
new
или из конструктора? Какая память - выделена под строящийся объект или какая-то память выделена внутри конструктора?