我正在学习之后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
:-
第 1 部分 (#X)
可以使用特定类型分配器改进此代码段。
它可以在 2 个位置进行改进。 (
#X
和 #Y
)std::vector<C>
在线 #X
似乎是一个堆栈变量,所以我应该使用
stack allocator
:-std::vector<C,StackAllocator> result; //#X
这往往会产生性能增益。 (
#X
已完成。)第 2 部分 (#Y)
接下来,更难的部分在
B()
构造函数。 ( #Y
)如果变量
bField
就好了有适当的分配协议(protocol)。仅对调用者进行编码以显式使用分配器是无法实现的,
因为构造函数的调用者只能做到最好:-
std::allocator<B> bAllo;
B* b = bAllo.allocate(1);
对
bField
的分配协议(protocol)没有任何影响.因此,选择正确的分配协议(protocol)是构造函数本身的职责。
第 3 部分
我不知道是否是
B
的实例将被构造为堆变量或堆栈变量。这很重要,因为此信息对于选择正确的分配器/协议(protocol)很重要。
如果我知道它是哪一个(堆或堆栈),我可以更改
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);
}
};
题
#X
和 #Y
)? 很难找到使用分配器的实际示例。
编辑 (回复沃尔特)
... using another than std:allocator<> is only rarely recommendable.
对我来说,这是沃尔特回答的核心。
如果它是可靠的,那将是一种宝贵的知识。
1. 有没有书/链接/引用/证据支持它?
该列表不支持该声明。 (它实际上支持相反的一点。)
是不是来自个人经验?
2. 答案在某种程度上与许多来源相矛盾。请辩护。
有很多来源建议不要使用
std:allocator<>
.无法回答“您为子系统 X 使用了多少内存?”是有罪的。
这意味着自定义分配器是主机游戏的必需品。
@ 部分“为什么要替换默认分配器?”
没有自定义分配器 =“时不时会有一点延迟(在游戏中)”
更具体地说,它们只是一种在现实世界中很少值得使用的“炒作”吗?
另一个小问题:-
是否可以将声明扩展为“大多数质量的游戏很少使用自定义分配器”?
3.如果我遇到这种罕见的情况,我必须支付费用,对吗?
只有两种好方法:-
这是正确的吗?
最佳答案
在 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<>
只有在有充分理由的情况下才值得推荐。可能的情况如下。为回答其他问题而添加的注释。
文章Are we out of memory日期从 2008 年开始,不适用于当代 C++ 实践(使用 C++11 标准或更高版本),当使用
std
进行内存管理时容器和智能指针( std::unique_ptr
和 std::shared_ptr
)避免了内存泄漏,这是在编写不佳的代码中增加内存需求的主要来源。在为某些特定应用程序编写代码时,可能有充分的理由使用自定义分配器——而 C++ 标准库支持这一点,因此这是一种合法且适当的方法。好的理由包括上面已经列出的那些,特别是当在多线程环境中需要高性能或通过 SIMD 指令实现时。
如果内存非常有限(如某些游戏机上可能如此),则自定义分配器无法真正神奇地增加内存量。所以在这种情况下,分配器的使用,而不是分配器本身,才是最关键的。不过,自定义分配器可能有助于减少内存碎片。
关于c++ - 我应该将分配器作为函数参数传递吗? (我对分配器的误解),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/41921316/