Поддерживают ли контейнеры стандартной библиотеки (STL) форму выделения nothrow? - PullRequest
14 голосов
/ 28 января 2011

Оператор 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 :: Containers, или мне всегда придется обрабатывать (ожидаемое !!) исключение с std::vector и друзьями?


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


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


Редактировать: Что касается , почему Я даже спрашиваю:

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

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

Ответы [ 3 ]

15 голосов
/ 28 января 2011

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

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

6 голосов
/ 28 января 2011

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

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

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

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

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

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

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

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

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

2 голосов
/ 28 января 2011

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

Что касается "простого и эффективного", я думаю, что контейнеры 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 ...

На четыре строки короче, чем ваш исходный код, и вызывающая сторона, которая в настоящее время должна обрабатывать "извени_из_платной_метки_would_you_like_to_try_again", может обработать исключение.Если этот код ошибки пропущен через несколько уровней вызывающих абонентов, вы можете сохранить четыре строки на каждом уровне.В C ++ есть исключения, и почти во всех целях вы можете принять это и написать соответствующий код.

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

...