【发布时间】:2016-07-29 07:39:23
【问题描述】:
我正在设计一个类似std::vector 的类用于自学目的,但我遇到了构造函数内部内存分配的难题。
std::vector 的容量构造函数非常方便,但代价是有可能抛出 std::bad_alloc 异常,并用它来破坏整个程序。
我正在努力决定什么是最优雅的方式来处理容量构造函数失败的不太可能的情况,或者最好地通知用户通过使用构造函数,他们同意数据结构能够删除整个程序通过异常。
我的第一个想法是在调用构造函数时添加一个编译时警告,提醒构造函数可能会失败,并且他们应该确保处理它,或者注意使用构造函数所涉及的风险。
这个解决方案看起来很糟糕,因为如果在全球范围内应用,会导致过多的警告,并给人留下不好的印象。
我的第二个想法是将构造函数设为私有,并要求通过静态“requestConstruct”-like 方法访问构造函数,类似于 Singleton 模式。
这个解决方案让界面看起来很奇怪。
我还有一些想法,但似乎都破坏了界面的“感觉”。这些想法是:
- 类似升压的
boost::optional - 类似 Haskell 的可能
- 强制用户给我一个结构被授权的内存池。这个想法看起来很优雅,但我找不到 C/C++ 的干净/流行的静态内存池实现。不过,我确实在 https://github.com/CodeDmitry/MemoryPools 上成功进行了测试。
- 使用描述性断言为炸毁世界道歉,并详细解释发生的事情。这就是我正在做的at the moment。
- 在调用它退出之前尝试再分配几次并使整个程序崩溃。这似乎是一个很好的策略,但感觉更像是交叉手指,因为程序仍然可能由于结构的行为(而不是程序)而崩溃。这种方法可能只是偏执和错误的,因为分配失败不一定会给您“重新分配”的机会,只会关闭程序。
- 发明某种压力测试机制,让结构的所有者相信它可以处理用户期望的最大容量(这可能很难,因为这些压力测试可能会产生很大的误导,因为内存是可用的现在,但在内存更密集的时候,它可能不会)。
还有一个有趣的可能性是没有足够的内存来实际捕获异常。这个程序似乎没有捕捉到它(这可能与是否有足够的内存有关)。
#include <stdint.h>
#include <exception>
#include <iostream>
#include <stdlib.h>
int main(int argc, char **argv)
{
uint_fast32_t leaked_bytes = 0;
while (1) {
try {
new uint8_t;
} catch (const std::bad_alloc& e) {
std::cout << "successfully leaked" << leaked_bytes << " bytes." << '\n';
exit(0);
} catch (const std::exception& e) {
std::cout << "I caught an exception, but not sure what it was...\n";
exit(0);
}
++leaked_bytes;
}
}
这个程序确实让我在程序终止之前处理失败:
#include <stdlib.h>
#include <stdint.h>
#include <stdio.h>
int main(int argc, char **argv)
{
uint_fast32_t bytes_leaked = 0;
while (1) {
if (malloc(1) == 0) {
printf("leaked %u bytes.\n", bytes_leaked);
exit(0);
}
++bytes_leaked;
}
return 0;
}
nothrow 也能正常工作:
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <new>
int main(int argc, char **argv)
{
uint64_t leaked_bytes = 0;
while (1) {
uint8_t *byte = new (std::nothrow) uint8_t;
if (byte == nullptr) {
printf("leaked %llu bytes.\n", leaked_bytes);
exit(0);
}
++leaked_bytes;
}
return 0;
}
编辑:
我想我找到了解决这个问题的方法。在主进程上存储动态数据结构存在固有的幼稚。也就是说,这些结构并不能保证一定会成功,并且可能随时中断。
最好在另一个进程中创建所有动态结构,并在出现意外问题时制定某种重启策略。
这确实增加了与数据结构通信的开销,但它使主程序不必管理数据结构可能出现的所有问题,从而可以迅速接管 main 中的大部分代码。
结束编辑。
是否有适当的方法来处理此类动态类中的分配问题,提高我的用户对这些风险的认识?
【问题讨论】:
-
据我所知 std::vector 没有容量构造函数。它有 size 构造函数,它实际上分配对象而不仅仅是保留内存
-
对我来说,抛出
std::bad_alloc是报告“内存不足”问题的最惯用方式。毕竟,如果无法获取内存,您的构造函数实际上无法完成其工作。 -
做标准库做的事情。相信我,许多大胆的新人都试图做得更好。
-
构造函数异常不会“取消整个程序”。他们可以很好地被抓住,你可以继续执行。如果内存不足,也许它应该“删除整个程序”,未被捕获。也许无效的输入会导致它请求不合理数量的内存,这会降低整个机器的速度,未经检查。
-
我完全不明白这一点。您可以将构造函数放在
try和catch块中吗?否则,如果程序无法正常执行,是否要继续?
标签: c++ exception memory-management constructor malloc