我应该将allocator作为函数参数传递吗?(我对分配器的误解)

jav*_*ver 15 c++ memory memory-management allocation c++11

在我allocator通过阅读一些文章
(cppreference我们内存不足)来研究几天之后,
我对如何控制数据结构以某种方式分配内存感到困惑.

我很确定我误解了一些东西,
所以我将把剩下的问题分成很多部分,以便让我的错误更容易被引用.

这是我(错误)理解的: -

片段

假设这B::generateCs()是一个C从列表中生成列表的函数CPrototype.
B::generateCs()被使用B()的构造函数: -

class C          {/*some trivial code*/};
class CPrototype {/*some trivial code*/};
class B {
    public: 
    std::vector<C> generateCs() {  
        std::vector<CPrototype> prototypes = getPrototypes();
        std::vector<C> result;                     //#X
        for(std::size_t n=0; n < prototypes.size(); n++) {
            //construct real object  (CPrototype->C)
            result.push_back( makeItBorn(prototypes[n]) ); 
        }
        return result;
    }
    std::vector<C> bField;    //#Y
    B() {
        this->bField = generateCs();    //#Y  ; "generateCs()" is called only here
    }
    //.... other function, e.g. "makeItBorn()" and "getPrototypes()"
};
Run Code Online (Sandbox Code Playgroud)

从上面的代码,std::vector<C>目前使用通用默认值std::allocator.

为简单起见,从现在开始,假设只有2个分配器(旁边std::allocator),
我可以自己编写代码或从某处修改 : -

  • HeapAllocator
  • StackAllocator

第1部分(#X)

可以使用特定类型分配器来改进此代码段.
它可以在2个地方进行改进.(#X#Y)

std::vector<C>at line #X似乎是一个堆栈变量,
所以我应该使用stack allocator : -

std::vector<C,StackAllocator> result;   //#X
Run Code Online (Sandbox Code Playgroud)

这往往会带来性能提升.(#X已完成.)

第2部分(#Y)

接下来,更难的部分是B()构造函数.(#Y)
如果变量bField具有适当的分配协议,那将是很好的.

只是将调用者编码为显式使用allocator无法实现它,因为构造函数的调用者只能做到:

std::allocator<B> bAllo;   
B* b = bAllo.allocate(1);   
Run Code Online (Sandbox Code Playgroud)

这对分配协议没有任何影响bField.

因此,构造函数本身有责任选择正确的分配协议.

第3部分

我不知道是将一个实例B构造为堆变量还是堆栈变量.
这很重要,因为这些信息对于选择正确的分配器/协议非常重要.

如果我知道它是哪一个(堆或堆栈),我可以更改声明bField: -

std::vector<C,StackAllocator> bField;     //.... or ....
std::vector<C,HeapAllocator> bField;     
Run Code Online (Sandbox Code Playgroud)

不幸的是,由于信息有限(我不知道它将是堆/堆栈,它可能都是),
这条路径(使用std::vector)导致死路.

第4部分

因此,更好的方法是将allocator传递给构造函数: -

MyVector<C> bField; //create my own "MyVector" that act almost like "std::vector"
B(Allocator* allo) {
    this->bField.setAllocationProtocol(allo);  //<-- run-time flexibility 
    this->bField = generateCs();   
}
Run Code Online (Sandbox Code Playgroud)

这很乏味,因为调用者必须将分配器作为附加参数传递,
但没有其他方法.

此外,当有许多呼叫者时,它是获得以下数据一致性优势的唯一实用方法,每个呼叫者都使用自己的内存块: -

class System1 {
    Allocator* heapForSystem1;
    void test(){
        B b=B(heapForSystem1);
    }
};
class System2 {
    Allocator* heapForSystem2;
    void test(){
        B b=B(heapForSystem2);
    }
};
Run Code Online (Sandbox Code Playgroud)

  • 我从哪里开始出错,怎么样?
  • 如何改进代码段以使用适当的分配器(#X#Y)?
  • 我什么时候应该将allocator作为参数传递?

很难找到使用allocator的实际例子.

编辑(回复沃尔特)

...使用另一个而不是std:allocator <>很少值得推荐.

对我来说,这是沃尔特答案的核心.
如果它是可靠的,那将是一个有价值的知识.

1.是否有支持它的书/链接/参考/证据?
该列表不支持该声明.(它实际上支持相反的一点.)
是否来自个人经历?

答案在某种程度上与许多来源相矛盾.请防守.
有许多消息来源建议不要使用std:allocator<>.

更具体地说,它们只是一种在现实世界中很少值得使用的"炒作"吗?

另一个小问题: -
声明可以扩展到"大多数高质量的游戏很少使用自定义分配器"吗?

如果我处于如此罕见的境地,我必须付出代价,对吧?

只有两种好方法: -

  • 传递allocator作为模板参数,或
  • 作为函数(包括构造函数)参数
  • (另一个不好的方法是创建一个关于使用什么协议的全局标志)

这是对的吗?

Wal*_*ter 8

在C++中,用于标准容器的分配器与容器类型相关联(但见下文).因此,如果要控制类的分配行为(包括其容器成员),则分配器必须是该类型的一部分,即您必须将其作为template参数传递:

template<template <typename T> Allocator>
class B
{
public:
  using allocator = Allocator<C>
  using fieldcontainer = std::vector<C,allocator>;
  B(allocator alloc=allocator{})
  : bFields(create_fields(alloc)) {}
private:
  const fieldcontainer bFields;
  static fieldcontainer create_fields(allocator);
};
Run Code Online (Sandbox Code Playgroud)

但请注意,存在实验性多态分配器支持,它允许您独立于类型更改分配器行为.这当然比设计自己的MyVector<>模板更可取.

请注意,std::allocator<>如果有充分的理由,请使用其他名称.可能的情况如下.

  1. 对于频繁分配和解除分配的小对象,堆栈分配器可能是首选,但即使堆分配器的效率也可能不高.

  2. 一个分配器,提供与64字节对齐的内存(适用于对齐加载到AVX寄存器中).

  3. 一个高速缓存对齐分配器是为了避免在多线程的情况下错误共享有用.

  4. 分配器可以避免默认初始化简单的可构造对象,以增强多线程设置中的性能.


注意添加以回应其他问题.

文章是我们从2008年开始的内存日期,并不适用于当代C++实践(使用C++ 11标准或更高版本),当使用std容器和智能指针(std::unique_ptrstd::shared_ptr)的内存管理避免内存泄漏时,这是主要的代码写得不好的内存需求增加的来源.

在为某些特定应用程序编写代码时,可能有充分的理由使用自定义分配器 - 而C++标准库支持这一点,因此这是一种合法且适当的方法.好的理由包括上面列出的那些,特别是当在多线程环境中需要高性能或通过SIMD指令实现时.

如果内存非常有限(因为它可能在某些游戏控制台上),自定义分配器无法真正神奇地增加内存量.因此,在这种情况下,分配器的使用,而不是分配器本身,是最关键的.但是,自定义分配器可能有助于减少内存碎片.