Стоит ли делать мой код менее читабельным для обеспечения безопасности исключений в случае ошибок нехватки памяти? - PullRequest
0 голосов
/ 13 февраля 2020

У меня есть игра с не копируемыми Item с, поскольку они должны быть уникальными:

class Item {
  Item() noexcept;
  Item(Item&&) noexcept;
  Item(Item const&) = delete;
// ...
};

class Creature способен получить Item и добавить его к своим inventory:

void Creature::receive_item(Item&& item) noexcept {
  this->inventory.push_back(std::move(item));
}

void caller_code() {
  Creature creature;
  Item item;
  // ...
  creature.receive_item(std::move(item));
}

Этот подход кажется красивым и чистым, но есть небольшая проблема: если какая-то часть моего кода может восстановиться после std::bad_alloc и, следовательно, перехватывает одну из recieve_item() * push_back(), логика c в игре не определена: Item был перемещен в функцию, которая не смогла сохранить одну, поэтому она просто потерялась. Более того, спецификатор noexcept должен быть удален только для этой возможности.

Таким образом, я могу обеспечить безопасность исключений таким образом (укажите, если я ошибаюсь):

void Creature::receive_item(Item& item) {
  this->inventory.resize(this->inventory.size() + 1);
  // if it needs to allocate and fails, throws leaving the item untouched
  this->inventory.back() = std::move(item);
}

void caller_code() {
  Creature creature;
  Item item;
  // ...
  creature.receive_item(item); // no moving
}

Однако теперь у нас появились новые недостатки:

  • отсутствие std::move() в коде вызывающего абонента скрывает факт перемещения item;
  • в размер изменения (+ 1) «часть просто безобразна и может быть неправильно понята при просмотре кода.

Вопрос в названии. Является ли безопасность исключений даже для такого странного случая хорошей конструкцией?

1 Ответ

1 голос
/ 13 февраля 2020

Ответ на ваш вопрос зависит от того, можете ли вы и хотите обрабатывать ошибки выделения памяти, кроме как при сбое. Если вы это сделаете, вам придется принять меры, помимо того, чтобы поймать std::bad_alloc. Большинство современных операционных систем реализуют переполнение памяти , что означает, что выделение памяти будет выполнено успешно, но доступ к выделенной памяти в первый раз вызовет сбой страницы, что обычно приводит к взлому sh. Вы должны явно отказать в выделенных страницах в вашем распределителе памяти, чтобы обнаружить состояние нехватки памяти, прежде чем возвращать указатель вызывающей стороне.

Что касается изменения кода, вам не нужно изменять свой вызов на push_back:

this->inventory.push_back(std::move(item));

Если push_back (или любому другому потенциально перераспределяющемуся методу) необходимо выделить новый буфер, он сделает это до того, как переместит новый элемент в вектор. Очевидно, это потому, что в векторе нет места для перемещения нового элемента. И когда буфер перераспределяется и все существующие элементы перемещаются / копируются в новый буфер, новый элемент перемещается в качестве последнего шага.

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

...