为了在内存不足错误的情况下提供异常安全,是否值得降低我的代码的可读性?
Is it worth making my code less readable for providing exception safety in case of out of memory errors?
我有一个 non-copyable 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()
抛出的代码, in-game 逻辑未定义:一个 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
; 的事实
- "resize(+1)" 部分很难看,在代码审查期间可能会被误解。
问题在标题里。即使对于这样一个奇怪的案例,异常安全也被认为是一个好的设计吗?
您的问题的答案取决于您是否能够并且想要处理内存分配错误,而不是崩溃。如果你这样做,你将不得不采取不仅仅是捕捉 std::bad_alloc
的措施。大多数现代操作系统都实现 memory overcommit,这意味着内存分配会成功,但第一次访问分配的内存会导致页面错误,这通常会导致崩溃。在将指针返回给调用者之前,您必须在内存分配器中显式错误分配页面以检测内存不足情况。
关于您的代码修改,您不必修改对push_back
的调用:
this->inventory.push_back(std::move(item));
如果push_back
(或任何其他潜在的重新分配方法)需要分配新的缓冲区,它会在将新项目移动到向量中之前执行此操作。显然,这是因为向量中没有空间可以移动新元素。当缓冲区被重新分配并且所有现有元素都moved/copied到新缓冲区时,新元素被移动作为最后一步。
换句话说,如果 push_back
抛出,您可以确定新项目没有被移动。如果它没有抛出并且 returns 正常,则该项目被移出。
我有一个 non-copyable 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()
抛出的代码, in-game 逻辑未定义:一个 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
; 的事实
- "resize(+1)" 部分很难看,在代码审查期间可能会被误解。
问题在标题里。即使对于这样一个奇怪的案例,异常安全也被认为是一个好的设计吗?
您的问题的答案取决于您是否能够并且想要处理内存分配错误,而不是崩溃。如果你这样做,你将不得不采取不仅仅是捕捉 std::bad_alloc
的措施。大多数现代操作系统都实现 memory overcommit,这意味着内存分配会成功,但第一次访问分配的内存会导致页面错误,这通常会导致崩溃。在将指针返回给调用者之前,您必须在内存分配器中显式错误分配页面以检测内存不足情况。
关于您的代码修改,您不必修改对push_back
的调用:
this->inventory.push_back(std::move(item));
如果push_back
(或任何其他潜在的重新分配方法)需要分配新的缓冲区,它会在将新项目移动到向量中之前执行此操作。显然,这是因为向量中没有空间可以移动新元素。当缓冲区被重新分配并且所有现有元素都moved/copied到新缓冲区时,新元素被移动作为最后一步。
换句话说,如果 push_back
抛出,您可以确定新项目没有被移动。如果它没有抛出并且 returns 正常,则该项目被移出。