在 C++ 中为堆栈分配的变量编写不同的构造函数/析构函数

Pau*_*Hat 5 c++ memory heap-memory

我希望为类的堆栈分配实例调用一个特殊的构造函数/析构函数,并且:

  1. 禁止所有其他情况(我认为这是不可能的)。

或者

  1. 让他们调用不同的构造函数/析构函数。

这可以移植吗?

我为什么要这样做?
我正在编写一个容器类库,其中包含一个潜在的大型动态分配缓冲区。
从概念上讲,它应该像这样工作:

class SArray(){
    size_t size;
    char* data;
    SArray(size_t _size):size(_size){
        data = (char*)malloc(size);
    }

    ~SArray(){
        free(data);
    }
... Some operators, etc. ...
}
Run Code Online (Sandbox Code Playgroud)

然而,这对我所在领域(高性能机器人)的用户来说是一个重大的放缓。当您从系统分配时,大分配首先映射到零页,当您写入它们时会导致页面错误,然后 - 出于安全原因 - 内核在将数据提供给您之前花费大量时间将数据清零。我的用户倾向于分配许多小缓冲区(sub-KB)和非常大的缓冲区(1-1000 MB)。

如果我们可以确定以 LIFO(stack) 顺序调用构造函数和析构函数,那么我们就可以拥有一个巨大的虚拟堆栈来进行内存分配:

class SArray(){
    static char backing_stack[1000000000000]; // 1TB, at least as big as all of RAM
    static sp = 0;
    size_t size;
    char* data;
    SArray(size_t _size):size(_size){
        data = backing_stack + sp;
        sp += size;
    }

    ~SArray(){
        sp -= size;
    }
... Some operators, etc. ...
}
Run Code Online (Sandbox Code Playgroud)

在这里,来自内核的页面错误和归零仅在我们第一次接触backing_stack[1] 的一部分时发生。

这个答案表明,LIFO 顺序是在 C++ 中为每种存储类型独立强制执行的,这意味着如果我们声明:

void foo(){
    static SArray static_decl(100);
    thread_local SArray tl_decl(1000);
    SArray local_decl(2000);
}
Run Code Online (Sandbox Code Playgroud)

如果他们都使用相同的backing_stack. 显而易见且首选的解决方案是让构造函数对不同的存储类型使用不同的堆栈。

如果这是不可能的,我们可以解决一个除了作为局部变量之外根本无法创建的类,只要我们可以自动检测到有人试图这样做并发出编译器错误。预分配的速度增益是如此之大,以至于我愿意在需要时禁止其他用途。

到目前为止,我发现你可以删除 new、placement new 和 delete 操作符,所以有人意外堆分配不是一个大问题。但是,毫无戒心的用户可以尝试创建静态或 thread_local 实例。临时和寿命延长似乎也可能带来问题。

我探索并拒绝的最后一个选项是编写基于哈希表的分配方案。存在的问题是,如果用户分配了许多小于页面大小的容器,内存就会碎片化。与调用堆栈相比,处理这个问题使得 malloc() 对于小项目如此缓慢。

[1]:我们不能使用 alloca() 或等价物,因为我们的分配通常太大,以至于它们超过了保护页,默默地破坏了我们的堆栈。