构造函数内部的内存分配?

时间:2016-04-08 06:19:42

标签: c++ memory-management constructor exception-handling malloc

我正在设计一个类似std::vector的课程用于自学教学目的,但我在构造函数内部遇到了一个难以解决的内存分配问题。

std::vector的容量构造函数非常方便,但是有可能抛出std::bad_alloc异常,并用它来删除整个程序。

我正在努力决定处理容量构造函数失败的不太可能的情况的最优雅方法,或者最好通知用户通过使用构造函数,他们同意数据结构能够取消整个计划都是例外。

我的第一个想法是每当调用构造函数时添加一个编译时警告,提醒构造函数可能失败,并且他们应该确保要么处理它,要么意识到使用构造函数所涉及的风险。

这个解决方案似乎很糟糕,因为如果在全球范围内应用它会引起太多警告,并给人留下不好的印象。

我的第二个想法是使构造函数变为私有,并且需要通过类似于Singleton模式的静态“requestConstruct”方法来访问构造函数。

此解决方案使界面看起来很奇怪。

我还有一些想法,但所有这些想法似乎都会破坏界面的“感觉”。这些想法是:

  • boost-like boost::optional
  • Haskell-like maybes
  • 强制用户给我一个结构被授权的内存池。这个想法似乎非常优雅,但我找不到一个干净/流行的静态内存池实现C / C ++。我确实成功地在https://github.com/dmitrymakhnin/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中的大部分代码。

结束编辑。

是否有正确的方法来处理此类动态类中的分配问题,提高用户对这些风险的认识?

1 个答案:

答案 0 :(得分:0)

根据提供的反馈,我确信没有优雅的方法来阻止管理动态内存的类在不引入复杂性的情况下删除程序。

抛出异常是有问题的,因为一旦你的类没有可以分配的内存,它就可能无法处理用户可以捕获的std :: bad_alloc。

从更多的思考,我开始意识到防止程序从它使用的模块崩溃的一种方法是将使用这些模块的程序部分移动到另一个进程,并让该进程与主进程,如果其他进程以某种方式发生故障,您可以重新启动该进程并发出另一个请求。

只要您使用任何能够在极端情况下失败的类,除非您能够控制其极端情况,否则无法阻止它们失败。

对于动态内存,很难准确控制进程可以访问的动态内存,它具有的分配策略以及应用程序使用的总内存量。因此,如果不严格控制您使用的连续内存池,或者将数据结构移动到另一个可以在失败时重新启动的进程进行管理,则很难对其进行控制。

答案:提供管理动态内存的类的简单接口/实现对本身是不可能的。你要么有一个简单的接口/实现,它将取消你的程序,如std :: vector;或复杂的接口/实现,需要以下之一或更聪明的东西:

  1. 程序员必须提供已有的内存块。由于记忆已经存在,你知道它有多少,你可以分配超出你能承受的数量。
  2. 数据结构使用自己的内存管理方案,使用单独的进程,而不是托管结构的进程。
  3. 数据结构由一个单独的进程管理,类似于数据库服务器。这样可以在失败的情况下轻松重启过程。