Подтвердить что ты не робот

Контейнеры стандартной библиотеки (STL) поддерживают форму выделения nothrow?

Оператор new (или для POD, malloc/calloc) поддерживает простую и эффективную форму сбоя при распределении больших блоков памяти.

Скажем, у нас есть это:

const size_t sz = GetPotentiallyLargeBufferSize(); // 1M - 1000M
T* p = new (nothrow) T[sz];
if(!p) {
  return sorry_not_enough_mem_would_you_like_to_try_again;
}
...

Есть ли такая конструкция для контейнеров std:: или мне всегда придется обрабатывать исключение (ожидаемое!!) с std::vector и друзьями?


Может быть, есть способ написать пользовательский распределитель, который предварительно распределяет память, а затем передает этот пользовательский распределитель на вектор, так что до тех пор, пока вектор не запрашивает больше памяти, чем вы помещаете в распределитель заранее, это не подведет?


Последующая мысль: в действительности нужна была функция-член bool std::vector::reserve(std::nothrow) {...} в дополнение к нормальной резервной функции. Но поскольку это имело бы смысл только в том случае, если распределители были расширены для того, чтобы допускать нечеткое распределение, этого просто не произойдет. Кажется, что (что-то новое) хорошо для чего-то в конце концов: -)


Изменить: Что касается того, почему я даже спрашиваю об этом:

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

Если new (nothrow) имеет законные цели использования, также будет иметь резерв vector-nothrow.

4b9b3361

Ответ 1

По умолчанию стандартные классы контейнеров STL используют класс std::allocator под капотом, чтобы выполнить их выделение, поэтому они могут бросать std::bad_alloc, если нет доступной памяти. Интересно отметить, что спецификация С++ ISO на распределителях заявляет, что возвращаемое значение любого типа распределителя должно быть указателем на блок памяти, способный удерживать некоторое количество элементов, что автоматически препятствует созданию пользовательского распределителя, который потенциально может использовать nothrow версии new, чтобы иметь подобные сбои молчащего выделения. Тем не менее, вы могли бы создать пользовательский распределитель, который завершил бы программу, если бы память не была доступна, так как тогда она неактуально верна, что возвращенная память действительна, когда память не остается.: -)

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

Ответ 2

Слишком часто мы слышим "Я не хочу использовать исключения, потому что они неэффективны".

Если вы не ссылаетесь на "внедренную" среду, в которой вы хотите отключить всю информацию о типе времени выполнения, вам не следует слишком беспокоиться о неэффективности исключений, если они выбрасываются соответствующим образом. Запуск из памяти является одним из следующих способов.

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

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

Могу ли я дать вам подсказку: если вы действительно выделяете такие большие объемы данных, тогда вектор, вероятно, является неправильным классом, и вместо этого вы должны использовать std:: deque. Зачем? Поскольку deque не требует непрерывного блока памяти, но все равно постоянный поиск по времени. И преимущества в два раза:

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

Когда я работал над такой системой в прошлом, мы обнаружили, что мы могли хранить в 4 раза больше данных, используя deque, поскольку мы могли использовать вектор из-за причины 1 выше, и это было быстрее из-за причины 2.

Что-то еще, что мы сделали, это выделить резервный буфер 2 МБ, и когда мы поймали bad_alloc, мы освободили буфер, а затем все равно покажем, что мы достигли емкости. Но с запасными 2 Мбайт мы, по крайней мере, знали, что у нас есть память для выполнения небольших операций для перемещения данных из памяти на временное дисковое хранилище.

Таким образом, мы иногда могли поймать bad_alloc и предпринять соответствующее действие, сохраняя согласованное состояние, которое является целью исключений, а не предполагать, что исчерпание памяти всегда фатально и никогда не должно делать ничего, кроме прекращения программы (или даже хуже, вызывать поведение undefined).

Ответ 3

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

Что касается "простых и эффективных", я думаю, что контейнеры std достаточно просты и разумно эффективны:

T* p = new (nothrow) T[sz];
if(!p) {
    return sorry_not_enough_mem_would_you_like_to_try_again;
}
... more code that doesn't throw ...
delete[] p;

try {
    std::vector<T> p(sz);
    ... more code that doesn't throw ...
} catch (std::bad_alloc) {
    return sorry_not_enough_mem_would_you_like_to_try_again;
}

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

Но даже лучше, как писать свои API для использования исключений:

std::vector<T> p(sz);
... more code that doesn't throw ...

Четыре строки короче, чем ваш исходный код, и вызывающий, который в настоящее время должен обрабатывать "sorry_not_enough_mem_would_you_like_to_try_again", может обрабатывать исключение. Если этот код ошибки передается через несколько уровней абонентов, вы можете сохранить четыре строки на каждом уровне. У С++ есть исключения, и для почти всех целей вы также можете принять это и написать код соответственно.

Относительно "(ожидается!)" - иногда вы знаете, как обрабатывать условие ошибки. Дело в том, что нужно поймать исключение. Это как исключения должны работать. Если код, который генерирует исключение, каким-то образом знал, что не было никого, кто когда-либо ловил его, тогда он мог бы прекратить выполнение программы.