通过阅读一些文章来研究allocator
几天后
(cppreference和Are we out of memory),
我对如何控制数据结构以某种方式分配内存感到困惑。
我很确定我误解了一些事情,
所以我将剩下的问题分成许多部分,以使我的错误更容易被引用。
这是我(错误)理解的: -
假设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()"
};
从上面的代码中,std::vector<C>
目前使用通用默认std::allocator
。
为简单起见,从现在开始,假设只有2个分配器(std::allocator
旁边),
我可以自己编写代码或从somewhere修改
: -
使用特定类型的分配器可以改进此代码段
它可以在2个地方进行改进。 (#X
和#Y
)
std::vector<C>
行的 #X
似乎是一个堆栈变量,
所以我应该使用stack allocator
: -
std::vector<C,StackAllocator> result; //#X
这往往会带来性能提升。 (#X
已完成。)
接下来,更难的部分在B()
构造函数中。 (#Y
)
如果变量bField
具有适当的分配协议,那将是很好的。
只是将调用者编码为明确使用allocator无法实现, 因为构造函数的调用者只能做到最好: -
std::allocator<B> bAllo;
B* b = bAllo.allocate(1);
对bField
的分配协议没有任何影响。
因此,构造函数本身有责任选择正确的分配协议。
我不知道B
的实例是构造为堆变量还是堆栈变量
这很重要,因为这些信息对于选择正确的分配器/协议非常重要。
如果我知道它是哪一个(堆或堆栈),我可以将bField
的声明更改为: -
std::vector<C,StackAllocator> bField; //.... or ....
std::vector<C,HeapAllocator> bField;
不幸的是,由于信息有限(我不知道它将是堆/堆栈,它可以是两者),
这条路径(使用std::vector
)导致死路。
因此,更好的方法是将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();
}
这很乏味,因为调用者必须将分配器作为附加参数传递,
但没有别的办法。
此外,当有许多呼叫者时,它是获得以下数据一致性优势的唯一实用方法,每个呼叫者都使用自己的内存块: -
class System1 {
Allocator* heapForSystem1;
void test(){
B b=B(heapForSystem1);
}
};
class System2 {
Allocator* heapForSystem2;
void test(){
B b=B(heapForSystem2);
}
};
#X
和#Y
)? 很难找到使用allocator的实际例子。
...使用另一个而不是std:allocator&lt;&gt;很少推荐。
对我而言,这是沃尔特答案的核心 如果它是可靠的,那将是一个有价值的知识。
1。是否有支持它的书/链接/参考/证据?
该列表不支持该声明。 (它实际上支持相反的一点。)
这是来自个人经历吗?
2。答案在某种程度上与许多来源相矛盾。请辩护。
有许多消息来源建议不要使用std:allocator<>
。
更具体地说,它们只是一种在现实世界中很少值得使用的“炒作”吗?
另一个小问题: - 可以将声明扩展为“大多数高质量游戏很少使用自定义分配器”吗?
第3。如果我处于如此罕见的情况,我必须支付费用,对吗?
只有两种好方法: -
这是对的吗?
答案 0 :(得分: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);
};
但请注意,存在实验polymorphic allocator support,它允许您独立于类型更改分配器行为。这当然比设计自己的MyVector<>
模板更好。
请注意,只有在有充分理由的情况下,才建议使用std::allocator<>
之外的其他人。可能的情况如下。
对于频繁分配和解除分配的小对象,堆栈分配器可能是首选,但即使堆分配器的效率也可能不高。
一个分配器,提供与64字节对齐的内存(适合对齐加载到AVX寄存器中)。
cache-aligned allocator有助于避免多线程情况下的误共享。
分配器可以avoid default initialising trivially constructible个对象来增强多线程设置的性能。
注意添加以回应其他问题。
文章Are we out of memory可追溯到2008年,当使用std
容器和智能指针进行内存管理时,不适用于当代C ++实践(使用C ++ 11标准或更高版本) std::unique_ptr
和std::shared_ptr
)避免了内存泄漏,这是在编写糟糕的代码中增加内存需求的主要原因。
在为某些特定应用程序编写代码时,可能有充分的理由使用自定义分配器 - 而C ++标准库支持这一点,因此这是一种合法且适当的方法。好的理由包括上面列出的那些,特别是在多线程环境中需要高性能或通过SIMD指令实现的时候。
如果内存非常有限(可能在某些游戏控制台上),自定义分配器无法真正神奇地增加内存量。因此,在这种情况下,分配器的使用,而不是分配器本身,是最关键的。但是,自定义分配器可能有助于减少内存碎片。
答案 1 :(得分:2)
听起来你误解了堆栈分配器是什么。堆栈分配器只是一个使用堆栈(数据结构)的分配器。堆栈分配器可以管理在堆栈或堆上分配的内存。如果你不知道你在做什么是危险的,因为当调用deallocate时,堆栈分配器会释放超过指定指针的所有内存。您可以使用堆栈分配器,以便在数据结构中最近初始化的元素始终是下一个被销毁的元素时(或者如果您最终在最后一次销毁它们)。
您可以查看一些std集合,了解它们如何允许程序员提供指定的分配器,例如std::vector。它们使用可选的模板参数,因此用户可以选择allocator类。它还允许您根据需要将分配器作为实例传递。如果不这样做,它将使用默认构造函数实例化一个。如果你没有选择一个allocator类,那么它使用只使用堆的默认分配器。你也可以这样做。
template<typename C, typename Allocator = std::allocator<C> >
class B {
vector<C, Allocator> bField;
void generateCs() {
std::vector<CPrototype> prototypes = getPrototypes();
for(std::size_t n=0; n < prototypes.size(); n++) {
//construct real object (CPrototype->C)
bField.push_back( makeItBorn(prototypes[n]) );
}
}
B(const Allocator& allo = Allocator()) : bField(allo) {
generateCs();
}
}
这允许用户在他们想要的时候控制分配,但是如果他们不在乎也会忽略它