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

Dmi*_*try 5 c++ malloc constructor memory-management exception-handling

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

std::vector行为能力构造器是很方便,但也是有潜在的成本抛出一个std::bad_alloc异常,并采取了你的整个程序吧.

我奋力来决定什么是处理能力构造失败的情况不太可能发生的最优雅的方式,或最好的通知使用构造,他们同意该数据结构能够拿下整个程序的用户通过例外.

我首先想到的是添加一个编译时警告,只要调用构造函数,提醒,构造函数可能会失败,他们应该确保无论是处理它,或了解有关使用构造函数中的风险.

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

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

该解决方案使界面看起来很奇怪.

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

  • 提振样 boost::optional
  • 类似Haskell的maybes
  • 强迫用户给我一个结构被授权的内存池.这个想法似乎非常优雅,但我找不到一个干净/流行的静态内存池实现C/C++.我确实成功地在https://github.com/dmitrymakhnin/MemoryPools进行了测试.
  • 使用描述性断言为炸毁世界道歉,并详细解释发生的事情.这是我做的那一刻.
  • 在调用它之前尝试再分配几次并退出整个程序.这似乎是一个很好的策略,但感觉更像是交叉手指,因为程序仍然会因结构的行为(而不是程序)而崩溃.这种方法可能只是偏执和错误,因为失败的分配不一定会给你"重新分配"的机会,并且只会关闭程序.
  • 创造某种压力测试机制,给信心结构的所有者,它能够处理最容量用户希望(这可能很难,因为这些压力测试可以是非常误导,因为内存现在可用,但在更多的内存密集时期,它可能不会).

还有一个很有趣的可能性就是没有足够的内存来实际捕获异常.这个程序似乎没有抓住它(这可能与有足够的内存有关,也可能没有).

#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;     
    }
}
Run Code Online (Sandbox Code Playgroud)

这个程序让我在程序终止之前处理失败,但是:

#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;
}
Run Code Online (Sandbox Code Playgroud)

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;
}
Run Code Online (Sandbox Code Playgroud)

编辑:

我想我找到了解决这个问题的方法.在主流程上存储动态数据结构存在固有的天真.也就是说,这些结构不能保证成功,并且可能在任何时候都会破裂.

最好在另一个进程中创建所有动态结构,并在出现意外问题时使用某种重启策略.

这确实会增加与数据结构通信的开销,但它会阻止主程序管理数据结构可能出错的所有内容,这可以快速占用main中的大部分代码.

结束编辑.

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

Dmi*_*try 1

根据提供的反馈,我确信没有一种优雅的方法可以防止管理动态内存的类在不引入复杂性的情况下破坏您的程序。

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

经过更多思考,我意识到防止程序因它使用的模块而崩溃的一种方法是将使用这些模块的程序部分移动到另一个进程,并让该进程与主进程共享内存,这样,如果另一个进程以某种方式出现故障,您可以重新启动该进程并发出另一个请求。

只要您使用任何能够在极端情况下失败的类,就不可能阻止它们失败,除非您能够控制它们的极端情况。

对于动态内存,很难准确控制进程访问动态内存的方式、分配策略以及应用程序总共使用了多少内存。因此,如果不对您使用的连续内存池进行更严格的控制,或者将数据结构移至另一个可以在失败时重新启动的进程来管理,就很难控制它。

回答:本质上不可能同时提供管理动态内存的类的简单接口/实现对。您要么有一个简单的接口/实现来删除您的程序,例如 std::vector; 或复杂的接口/实现,需要以下之一或更聪明的东西:

  1. 程序员必须提供已经可用的内存块。由于内存已经存在并且您知道自己有多少内存,因此您不能分配超出您承受能力的内存。
  2. 该数据结构使用自己的内存管理方案,使用一个会崩溃的单独进程,而不是该结构所在的进程。
  3. 数据结构完全由单独的进程管理,类似于数据库服务器。这使得在失败时可以轻松地重新启动该过程。

  • “抛出异常是值得怀疑的”。如果您不信任您的语言及其标准库设计者,也许您选择了错误的语言。 (3认同)