New (std::nothrow) против New в блоке try / catch


Я провел некоторое исследование после того, как узнал new, в отличие от malloc(), к которому я привык, не возвращает NULL для неудачных распределений, и обнаружил, что есть два различных способа проверить, удалось ли new или нет. Эти два способа таковы:

try
{
    ptr = new int[1024];
}
catch(std::bad_alloc& exc)
{
    assert();
};

И

ptr = new (std::nothrow) int[1024];
if(ptr == NULL) 
    assert();
Я верю, что эти два способа достигают одной и той же цели (поправьте меня, если я ошибаюсь, конечно!), поэтому мой вопрос таков:

Который является лучшим вариантом для проверки, если new удалось, полностью основанный на читабельности, ремонтопригодность и производительность, игнорируя при этом фактическое соглашение о программировании на языке c++.

4 38

4 ответа:

Считаю, что вы делаете. Вы распределяете память. И если по какой-то причине выделение памяти не может работать, вы assert. Что более или менее точно произойдет, если вы просто позволите std::bad_alloc распространиться обратно в main. В сборке выпуска, где assert является no-op, ваша программа аварийно завершит работу, когда она попытается получить доступ к памяти. Таким образом, это то же самое, что позволить исключению всплывать: остановка приложения.

Поэтому задайте себе вопрос: действительно ли вамнужно заботиться о том, что произойдет, если вы кончилась память? Если все, что вы делаете, это утверждение, то метод исключения лучше, потому что он не загромождает ваш код случайными asserts. вы просто позволяете исключению вернуться к main.

Если у вас действительно есть специальный кодовый путь в случае, если вы не можете выделить память (то есть вы можете фактически продолжать функционировать), исключения могут быть или не быть способом пойти, в зависимости от того, что такое кодовый путь. Если кодовый путь - это просто переключатель, установленный с помощью указателя null, то nothrow версия будет проще. Если вместо этого вам нужно сделать что-то совсем другое (вытащить из статического буфера, или удалить какой-то материал, или что-то еще), то ловить std::bad_alloc довольно хорошо.

Это зависит от контекста, в котором происходит выделение. Если ваша программа может продолжить работу даже в случае сбоя выделения (возможно, вернуть код ошибки вызывающему объекту), то используйте метод std::nothrow и проверьте значение NULL. В противном случае вы бы использовали исключения для потока управления, что не является хорошей практикой.

С другой стороны, если ваша программа абсолютно нуждается в том, чтобы эта память была успешно выделена для того, чтобы иметь возможность функционировать, используйте try-catch, чтобы перехватить (не обязательно в непосредственной близости окрестности new) исключения и изящно выйти из программы.

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

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

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

new используется для создания объектов, а не выделения памяти, поэтому ваш пример несколько искусственен.

Конструкторы объектов обычно бросают, если они терпят неудачу. Пройдя через реализацию new в Visual Studio более нескольких раз, я не верю, что код улавливает какие-либо исключения. Поэтому обычно имеет смысл искать исключения при создании объектов.

Я думаю std::bad_alloc выбрасывается только в случае сбоя части выделения памяти. Я не знаю, что именно. бывает, если вы передаете std::nothrow в new, но конструктор объекта бросает-в документах, которые я читал, есть неоднозначность.

Разница в производительности между двумя подходами, вероятно, не имеет значения, так как большая часть процессорного времени может быть легко потрачена в конструкторе объекта или поиске кучи. Эмпирическое правило не всегда уместно. Например, системы реального времени обычно ограничивают динамическое выделение памяти, поэтому new, если он присутствует, вероятно, будет перегружен. В в этом случае он может использовать возвращенный нулевой указатель и обрабатывать сбой локально.