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

问题描述 投票:11回答:2

我的问题基本上是跟进:

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池只返回基于sizeof(T)的特定大小的块。

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

我意识到Allocator<T>::rebind需要可互操作的拷贝构造函数,因此容器的用户不需要知道say的内部细节,链接列表节点类型等。但据我所知,standard itself似乎没有说任何如此严厉的要求,因为Allocator<U>构造的Allocator<T>必须与原来的Allocator<T>实例相等。

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

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

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,而不是a == b必须是真的!

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

Allocator<T> alloc1;
Allocator<U> alloc2(alloc1);
assert(alloc2 == alloc1); 

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

  1. accepted answer to this question说不然,回答者是一个声誉为108K的人
  2. 复制构造函数要求与标准中的相等要求之间的相互作用有点令人困惑,我可能误解了这个词。

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

c++ c++11 allocator
2个回答
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
投票

该标准不承认不同类型的对象(或值)可能真正平等的可能性;这与对象具有类型的想法相矛盾。然而,他们可以比较相等,这只是告诉我们在调用对象时operator==会返回什么。对于分配器模板的不同实例,指定在这些不同类型的对象之间调用该运算符有效地将第二个操作数替换为第一个操作数类型的对象,但是从第二个操作数中“复制构造”,然后应用operator==为这些相同类型的操作数。 (它逃避了我为什么类型被指定为Y::rebind<T>::other(不需要定义)而不是allocator_traits<Y>::rebind_alloc<T>,或更好甚至简单Xa的类型;可能只是一个历史遗留物。)所以最后,比较总是在相同类型的物体;对于该情况(仅),指定相等意味着互操作性,即,由一个分配器给出的存储可以使用另一个分配器来回收。

是的,在说Y b(a) XaAllocator<T>YAllocator<U>之后,确保b == a等于b == Y(a),返回true;还有a == b,等于a == X(b)必须返回true(你引用的段落如此明确地说,虽然有一些名称的交换)。但是,这并不意味着“如果我使用Allocator<T>分配一些内存,我必须能够使用Allocator<U>释放该内存”,因为该要求仅用于相同类型的分配器的相等性。实际上,目前还不清楚人们如何设置这样的释放,因为Allocator<U>::deallocate方法的第一个参数类型为Allocator<U>::pointer,大概是U*,并且在任何情况下都与Allocator<T>::pointer不同,所以人们只能进入这种情况之后做指针类型的讽刺,我相信当场给你未定义的行为。

我想指出的一个含义,有一种含义:分配器类型Allocator<T>几乎不能有任何类型取决于static的非T数据成员。 (“很难”,我的意思是它不是严格不可能的,只是不能以任何有用的方式进行。)因为如果这样的成员访问某些存储资源对将来分配类型T有用(想想指向T的指针到一些自由空间)假设分配器的相等性需要共享资源(因为一个分配器可以被释放到另一个),那么复制构造一个Allocator<U>必须以某种方式初始化相应的数据成员,该成员具有不同的类型,现在可以用作类型为U的资源或未来分配,但仍然保留资源的标识(因此它不能简单地为类型U构造空资源),因为复制构造回Allocator<T>必须导致资源与原始分配器共享;我认为这是一个非常高的命令。

我的结论是,在实践中,分配器必须是无状态的(根本不是非静态数据成员)或者具有类型无知的资源管理(特别是所有数据成员独立于value_type,并且所有复制构造不同类型的相同分配器共享资源的版本,就像malloc一次为所有类型服务)。或两者兼而有之,就像std::allocator一样。但我很想被证明是错的。

© www.soinside.com 2019 - 2024. All rights reserved.