据我所知,分配器的要求与STL一起使用 容器在C ++ 11标准的第17.6.3.5节的表28中列出。
我对其中一些要求之间的相互作用感到有些困惑。
给定X
类型作为类型T
的分配器,类型Y
是“
相应的分配器类“用于类型U
,实例a
,a1
和a2
该表格X
以及b
的实例Y
表示:
仅当分配了存储空间时,表达式a1 == a2
才会计算为true
来自a1
的{{1}}可由a2
解除分配,反之亦然。
表达式X a1(a);
格式正确,不会通过异常退出,
之后a1 == a
是真的。
表达式X a(b)
格式正确,不会通过异常退出,并且
之后a == b
。
我读到这句话说所有分配器必须是可复制构造的 副本可与原件互换的方式。更糟糕的是,同样的 跨类型边界是真的。这似乎是一个非常繁重的要求;如 据我所知,这使得大量类型的分配器变得不可能。
例如,假设我有一个我想在我的分配器中使用的freelist类,
为了缓存释放的对象。除非我遗漏了什么,否则我不能
在分配器中包含该类的实例,因为大小或
T
和U
的对齐方式可能不同,因此空闲列表条目也是如此
不兼容。
我的问题:
我的解释是否正确?
我在一些地方读到C ++ 11改进了对“有状态”的支持 分配器“。鉴于这些限制,情况如何?
您对如何做我正在尝试的事情有什么建议吗? 做?也就是说,如何在分配器中包含特定于分配类型的状态?
一般来说,分配器周围的语言似乎很草率。 (例如,
表28的序言说假设a
属于X&
类型,但有些类型为a
表达式重新定义{{1}}。)此外,至少GCC的支持是不符合要求的。
分配器周围的这种奇怪的原因是什么?它不经常发生
使用过的功能?
答案 0 :(得分:25)
分配器的平等并不意味着它们必须具有完全相同的内部状态,只是它们必须能够释放分配给任一分配器的内存。 类型a == b
的分配器a
和类型X
的分配器b
的分配器Y
的交叉类型相等定义表28“与a == Y::template rebind<T>::other(b)
相同”。换句话说,a == b
如果由a
分配的内存可以由重新绑定b
到a
的{{1}}实例化的分配器释放。
您的freelist分配器无需释放任意类型的节点,您只需要确保value_type
分配的内存可以由FreelistAllocator<T>
解除分配。鉴于在大多数理智的实现中FreelistAllocator<U>::template rebind<T>::other
与FreelistAllocator<U>::template rebind<T>::other
的类型相同,这很容易实现。
简单示例(Live demo at Coliru):
FreelistAllocator<T>
答案 1 :(得分:12)
1)我的解释是否正确?
你的自由列表可能不适合分配器,你是对的,它需要能够处理多种尺寸(和对齐)以适应。这是解决自由列表的问题。
2)我曾在一些地方读到C ++ 11改进了对有状态分配器的支持&#34;。考虑到这些限制,情况如何?
与出生相比,它没有那么多改进。在C ++ 03中,标准只是推动实现者提供可以支持非平等实例和实现者的分配器,有效地使有状态分配器不可移植。
3)你有什么建议可以做我想做的事吗?也就是说,如何在分配器中包含特定于分配类型的状态?
您的分配器可能必须是灵活的,因为您不应该确切地知道应该分配哪些内存(以及哪些类型)。此要求对于将您(用户)与使用分配器的某些容器(如std::list
,std::set
或std::map
)的内部隔离是必要的。
您仍然可以将此类分配器与简单容器一起使用,例如std::vector
或std::deque
。
是的,这是一项代价高昂的要求。
4)一般来说,分配器周围的语言似乎很草率。 (例如,表28的序言说假设a是X&amp;类型,但有些表达式重新定义了a。)此外,至少GCC的支持是不符合的。分配器周围的这种奇怪的原因是什么?它只是一种不经常使用的功能吗?
标准一般不容易阅读,不仅仅是分配器。你必须要小心。
要谨慎,gcc不支持分配器(它是编译器)。我猜你说的是libstdc ++(gcc附带的标准库实现)。 libstdc ++是 old ,因此它是为C ++ 03量身定制的。它已经适应了C ++ 11,但还不完全一致(例如,仍然使用Copy-On-Write作为字符串)。原因是libstdc ++非常注重二进制兼容性,C ++ 11所需的一些更改会破坏这种兼容性;因此,必须仔细介绍它们。
答案 2 :(得分:9)
我读到这一点时说,所有分配器必须是可复制构造的,使得副本可以与原件互换。更糟糕的是,跨类型边界也是如此。这似乎是一个非常繁重的要求;据我所知,这使得大量类型的分配器变得不可能。
如果分配器是一些内存资源的轻量级句柄,那么满足要求是微不足道的。只是不要尝试将资源嵌入到单个分配器对象中。
例如,假设我有一个我想在分配器中使用的freelist类,以便缓存释放的对象。除非我遗漏了某些内容,否则我无法在分配器中包含该类的实例,因为T和U的大小或对齐可能不同,因此freelist条目不兼容。
[allocator.requirements]第9段:
分配器可以约束可以实例化的类型以及可以调用其
construct
成员的参数。如果某个类型不能与特定分配器一起使用,则分配器类或对construct
的调用可能无法实例化。
除了给定类型T
之外,您的分配器可以拒绝为任何内容分配内存。这将阻止它在基于节点的容器中使用,例如std::list
需要分配它们自己的内部节点类型(不仅仅是容器的value_type
),但它适用于std::vector
。
这可以通过阻止分配器反弹到其他类型来完成:
class T;
template<typename ValueType>
class Alloc {
static_assert(std::is_same<ValueType, T>::value,
"this allocator can only be used for type T");
// ...
};
std::vector<T, Alloc<T>> v; // OK
std::list<T, Alloc<T>> l; // Fails
或者您只能支持适合sizeof(T)
的类型:
template<typename ValueType>
class Alloc {
static_assert(sizeof(ValueType) <= sizeof(T),
"this allocator can only be used for types not larger than sizeof(T)");
static_assert(alignof(ValueType) <= alignof(T),
"this allocator can only be used for types with alignment not larger than alignof(T)");
// ...
};
- 我的解释是否正确?
醇>
不完全。
- 我在一些地方读到C ++ 11改进了对“有状态分配器”的支持。鉴于这些限制,情况如何呢?
醇>
C ++ 11之前的限制更糟糕!
现在清楚地说明了分配器在复制和移动时如何在容器之间传播,以及当分配器实例被另一个可能与原始实例无法比较的实例替换时,各种容器操作的行为方式。没有这些澄清,不清楚如果发生什么应该发生你用有状态的分配器交换了两个容器。
- 你有什么建议可以做我想做的事吗?也就是说,如何在分配器中包含特定于分配类型的状态?
醇>
不要将它直接嵌入到分配器中,单独存储它并让分配器通过指针引用它(可能是智能指针,具体取决于您如何设计资源的生命周期管理)。实际的allocator对象应该是一些外部内存源的轻量级句柄(例如竞技场,游戏池或管理空闲列表的东西)。共享相同源的Allocator对象应该相等,即使对于具有不同值类型的分配器也是如此(见下文)。
我还建议您不要尝试支持所有类型的分配,如果您只需要支持它。
- 一般来说,分配器周围的语言似乎很草率。 (例如,表28的序言说假设a是X&amp;类型,但有些表达式重新定义了a。)
醇>
是的,正如您在https://github.com/cplusplus/draft/pull/334报告的那样(谢谢)。
此外,至少GCC的支持是不符合的。
这不是100%,但会在下一个版本中发布。
分配器周围的这种奇怪的原因是什么?它只是一种不经常使用的功能吗?
是。并且有许多历史包袱,并且很难指定广泛有用。我的ACCU 2012 presentation有一些细节,如果读完之后我会非常惊讶你认为你可以让它更简单; - )
关于分配器何时比较相等,请考虑:
MemoryArena m;
Alloc<T> t_alloc(&m);
Alloc<T> t_alloc_copy(t_alloc);
assert( t_alloc_copy == t_alloc ); // share same arena
Alloc<U> u_alloc(t_alloc);
assert( t_alloc == u_alloc ); // share same arena
MemoryArena m2
Alloc<T> a2(&m2);
assert( a2 != t_alloc ); // using different arenas
分配器相等的意思是对象可以释放彼此的内存,所以如果你从t_alloc
和(t_alloc == u_alloc)
分配一些内存是true
,那么这意味着你可以解除分配内存使用u_alloc
。如果它们不相等,则u_alloc
无法释放来自t_alloc
的内存。
如果您只有一个空闲列表,其中任何内存都可以添加到任何其他空闲列表中,那么您的所有分配器对象可能会相互比较。