【发布时间】:2017-06-14 17:58:39
【问题描述】:
在我通过阅读一些文章研究了几天allocator之后
(cppreference 和 Are we out of memory) ,
我对如何控制数据结构以某种方式分配内存感到困惑。
我很确定我误解了一些东西,
所以我会把剩下的问题分成许多部分,让我的错误更容易被引用。
这是我(错误)理解的:-
片段
假设B::generateCs() 是一个从CPrototype 列表中生成C 列表的函数。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修改
:-
- 堆分配器
- 堆栈分配器
第 1 部分 (#X)
可以使用特定类型的分配器来改进这个 sn-p。
它可以在 2 个位置进行改进。 (#X 和 #Y)
std::vector<C>#X 行似乎是一个堆栈变量,
所以我应该使用stack allocator :-
std::vector<C,StackAllocator> result; //#X
这往往会带来性能提升。 (#X 已完结。)
第 2 部分 (#Y)
接下来,更难的部分是B() 构造函数。 (#Y)
如果变量bField 具有适当的分配协议,那就太好了。
仅仅对调用者进行编码以显式使用分配器是无法实现的, 因为构造函数的调用者只能做到最好:-
std::allocator<B> bAllo;
B* b = bAllo.allocate(1);
对bField的分配协议没有任何影响。
因此,构造函数本身有责任选择正确的分配协议。
第 3 部分
我不知道B 的实例将被构造为堆变量还是堆栈变量。
这很重要,因为此信息对于选择正确的分配器/协议很重要。
如果我知道它是哪一个(堆或堆栈),我可以将bField 的声明更改为:-
std::vector<C,StackAllocator> bField; //.... or ....
std::vector<C,HeapAllocator> bField;
不幸的是,由于信息有限(我不知道它会是堆/堆栈,它可以是两者),
这条路径(使用std::vector)导致了死胡同。
第 4 部分
因此,更好的方法是将分配器传递给构造函数:-
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);
}
};
问题
- 我是从哪里开始出错的,怎么回事?
- 如何改进 sn-p 以使用适当的分配器(
#X和#Y)? - 什么时候应该将分配器作为参数传递?
很难找到使用分配器的实际示例。
编辑(回复 Walter)
... 使用 std:allocator 以外的其他方法很少推荐。
对我来说,这是沃尔特回答的核心。
如果它是可靠的,这将是一个有价值的知识。
1.是否有任何书籍/链接/参考/证据支持它?
该列表不支持该声明。 (它实际上有点支持相反的情况。)
是亲身经历吗?
2。答案在某种程度上与许多来源相矛盾。请防御。
有许多来源建议不要使用std:allocator<>。
-
Are we out of memory :
无法回答“您为子系统 X 使用了多少内存?”是有罪的。 -
Custom C++ allocators suitable for video games
这意味着自定义分配器对于主机游戏来说是必须的。
@ section "为什么要替换默认分配器?" -
Memory Management part 1 of 3
没有自定义分配器 =“时不时地有一点延迟(在游戏中)”
更具体地说,它们只是一种在现实世界中很少值得使用的“炒作”吗?
另一个小问题:-
是否可以将声明扩展到“大多数优质游戏很少使用自定义分配器”?
3.如果我遇到这种罕见的情况,我必须付出代价,对吧?
只有两个好方法:-
- 将分配器作为模板参数传递,或者
- 作为函数(包括构造函数)的参数
- (另一种不好的方法是创建一些关于使用什么协议的全局标志)
对吗?
【问题讨论】:
-
好的,我会更加努力让它更容易阅读。
-
std::vector<T>size()方法返回std::vector<T>::size_type(aka std::size_t)size()描述: cplusplus.com/reference/vector/vector/size 。我们什么时候应该使用 std::size_t: stackoverflow.com/questions/1951519/when-should-i-use-stdsize-t -
@javaLoverit 不能保证,但可以stackoverflow.com/questions/3901630/…
-
@Inline Awww,明白,但我还有另一个个人原因——我不想为了这个小小的性能而牺牲这个小小的可读性(减少 1 行)。
-
result被移动到generateCs的返回值,而generateCs又被移动到bField。 C++11 和标准的后续版本保证了这一点。填充result后,将不再进行复制或分配/解除分配。
标签: c++ c++11 memory memory-management allocation