C ++ 11有状态分配器是否可以跨类型边界互换?

时间:2014-12-14 16:59:37

标签: c++ c++11 allocator

我的问题基本上是跟进:

How can I write a stateful allocator in C++11, given requirements on copy construction?

基本上,尽管C ++ 11标准现在允许有状态分配器,但我们仍然要求如果您复制某个Allocator,则副本必须通过==进行比较。操作员与原件。这表明副本可以安全地解除分配由原始分配的内存,反之亦然。

所以,这样就可以禁止分配器维护独特的内部状态,比如slab-allocator或内存池等等。一种解决方案是对内部状态使用shared_ptr指针实现惯用法,以便某些原始Allocator的所有副本使用相同的底层内存池。那不算太糟糕。除了...

根据上面提到的问题,以及接受的答案,标准似乎要求Allocator<T>具有Allocator<U>的可互操作的复制构造函数,以便:

Allocator<T> alloc1;
Allocator<U> alloc2(alloc1);
assert(alloc1 == alloc2); // must hold true

换句话说,无论模板参数如何,分配器类型必须是可互操作的。这意味着如果我使用Allocator<T>分配一些内存,我必须能够使用从原始Allocator<U>构造的Allocator<T>实例来释放该内存。

...这对于任何尝试编写使用某种基于大小的内存池的分配器来说都是一个显示阻止,就像simple_segregated_storage池只返回基于{{1}的特定大小的块一样}。

但是......这是真的吗?

我意识到sizeof(T)需要可互操作的复制构造函数,因此容器的用户不需要知道say的内部细节,链接列表节点类型等。但就我所知,standard itself似乎并没有说任何如此强烈的要求,即Allocator<T>::rebind构造的Allocator<U>必须与原始Allocator<T>相等。实例。

标准基本上需要以下语义,其中 X Allocator<T>类型, a1 a2 是<的实例strong> X , Y Allocator<T>类型, b Allocator<U>的实例。

来自:§17.6.3.5(分配者要求)

Allocator<U>


因此,在我阅读此内容的过程中,a1 == a2 returns true only if storage allocated from each can be deallocated via the other. operator == shall be reflexive, symmetric, and transitive, and shall not exit via an exception. a1 != a2 : same as !(a1 == a2) a == b : same as a == Y::rebind<T>::other(b) a != b : same as !(a == b) X a1(a); Shall not exit via an exception. post: a1 == a X a(b); Shall not exit via an exception. post: Y(a) == b, a == X(b) 构建的Allocator<T>实例 不一定是可互换的。该标准仅要求Allocator<U>必须等同于a == b Y(a) == b必须 true

我认为对跨类型边界复制构造函数的要求使这一点令人困惑。但是,我读这个的方式,如果我有一个a == b,它必须有一个复制构造函数,它需要Allocator<T>,但暗示:

Allocator<U>

换句话说,我读这个的方式,上面的断言被允许失败。但我对此的理解并不自信,因为:

  1. accepted answer to this question另有说法,而回答者是一个声誉为108K的人

  2. 复制构造函数要求与标准中的相等要求之间的相互作用有点令人困惑,我可能会误解这个词汇。

  3. 所以,我在这里是正确的吗? (顺便说一句,boost::pool_allocator的实现似乎意味着我是正确的,假设boost开发人员关心标准合规性,因为这个分配器在类型边界上是不可互换的。)

2 个答案:

答案 0 :(得分:3)

您引用的最后一行:

  

X a(b);不能通过例外退出。发布:Y(a) == ba == X(b)

与你的结论发生冲突。

using X = Allocator<T>;
using Y = Allocator<U>;
Y b;
X a(b);
assert(Y(a) == b);
assert(a == X(b));
// therefore
assert(a == b);

答案 1 :(得分:1)

该标准不会带来不同类型的对象(或值)真正相等的可能性;这将与对象具有类型这一想法相矛盾。但是它们可以比较相等,这只是告诉我们operator==在被调用时会返回什么。对于分配器模板的不同实例,指定在这些不同类型的对象之间调用该运算符实际上将第二个操作数替换为第一个操作数但从第二个操作数“复制构造”的对象,然后应用operator==用于这些相等类型的操作数。 (这使我无法理解为什么将类型指定为Y::rebind<T>::other(不需要定义)而不是allocator_traits<Y>::rebind_alloc<T>,或更简单地说是X,即a的类型;可能因此,最后,比较始终是同一类型的对象之间的比较。对于这种情况(仅),指定了平等意味着互操作性,即,一个分配器给出的存储可以使用另一个分配器回收。

是的,在说出Y b(a)的类型XaAllocator<T>Y的{​​{1}}之后,可以确保Allocator<U>等于b == a,返回b == Y(a);也等于true的{​​{1}}必须返回a == b(您引用的段落如此明确地指出,尽管名称有些互换)。但是,不,这并不意味着“如果我使用a == X(b)分配一些内存,那么我必须能够使用true释放该内存”,因为该要求仅是为了使内存分配器相等。相同的类型。确实,尚不清楚如何为这样的释放进行设置,因为方法Allocator<T>具有类型为Allocator<U>的第一个参数,该参数可能为Allocator<U>::deallocate,并且在任何情况下都不同来自Allocator<U>::pointer,因此只有在完成指针类型的麻烦之后才能进入这种情况,我相信这会给您带来不确定的行为。

我想指出一种隐含的要求:分配器类型U*几乎没有任何非{Allocator<T>::pointer数据成员,其类型取决于{{1 }}。 (用“很难”来表示,这并不是严格不可能的,只是不能以任何有用的方式来做。)因为如果这样的成员访问一些对将来的Allocator<T>类型的分配有用的存储资源(请考虑指向{{ 1}}到一些可用空间),并假定分配器相等,要求共享此资源(因为可以将一个资源的分配重新分配给另一个资源),然后复制构造static必须以某种方式初始化相应的数据成员,一种不同的类型,它现在可以用作资源或类型T的将来分配,但仍保留该资源的标识(因此它不能简单地为类型T构造一个空资源),因为复制构造回T必须导致资源与原始分配器共享;我认为这是一个很高的要求。

我的结论是,实际上分配器必须是无状态的(根本没有非静态数据成员),或者必须具有类型不明的资源管理(尤其是所有数据成员都独立于Allocator<U>) ,并且使用同一资源分配器的不同类型的所有复制构造版本共享资源,就像U一次提供所有类型一样)。或两者皆有,例如U。但是我很想证明自己是错的。