c++ - Is it worth making my code less readable for providing exception safety in case of out of memory errors?
问题描述
I have a game with non-copyable Item
s as they are supposed to be unique:
class Item {
Item() noexcept;
Item(Item&&) noexcept;
Item(Item const&) = delete;
// ...
};
class Creature
is capable of receiving an Item
and adding it to their 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));
}
This approach seems nice and clean but there's a small problem: if any part of my code can recover after std::bad_alloc
and therefore catches one thrown by recieve_item()
's push_back()
, the in-game logic is undefined: an Item
was moved to a function which failed storing one so it is just lost. Moreover, the noexcept
specifier has to be removed just for this possibility.
So, I can provide exception safety this way (please point it out if I'm wrong):
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
}
However, now we have new disadvantages:
- the lack of
std::move()
in the caller's code obscures the fact of moving theitem
; - the "resize(+1)" part is just ugly and may be misunderstood during a code review.
The question is in the title. Is exception safety even for such a weird case considered a good design?
解决方案
您的问题的答案取决于您是否可以并且想要处理内存分配错误,而不是崩溃。如果这样做,您将不得不采取措施,而不仅仅是捕捉std::bad_alloc
. 大多数现代操作系统都实现了memory overcommit,这意味着内存分配会成功,但是第一次访问分配的内存会导致页面错误,这通常会导致崩溃。在将指针返回给调用者之前,您必须在内存分配器中显式地对分配的页面进行故障检测以检测内存不足的情况。
关于您的代码修改,您不必修改您的调用push_back
:
this->inventory.push_back(std::move(item));
如果push_back
(或任何其他可能的重新分配方法)需要分配一个新缓冲区,它将在将新项移动到向量中之前执行此操作。显然,这是因为向量中没有空间可以移动新元素。并且当缓冲区被重新分配并且所有现有元素被移动/复制到新缓冲区时,新元素将作为最后一步移动。
换句话说,如果push_back
抛出,您可以确定新项目没有被移动。如果它没有抛出并正常返回,则该项目被移出。
推荐阅读
- sql - 通过连接表来创建 SQL 视图是一种好习惯吗?
- reactjs - 如何使用 Redux-persist 传输或删除数据同步?
- javascript - 开玩笑找不到模块“配置”
- css - mini.css 模态对话框大小
- oracle-apex - Oracle Apex 18.2 中的富文本编辑器
- google-sheets - 转置值列跳过 Google 表格中的空白单元格
- c++ - 如何使用 ESP8266 向 Google Home 发送命令?
- linux - 如何将行从一个文件替换到另一个文件
- r - 字符串中的R变量
- postman - 如何在 Postman 请求正文中插入全局/环境变量?