【发布时间】:2012-09-11 23:57:04
【问题描述】:
带有std::allocator 的标准容器有其size_type defined as std::size_t。但是,是否可以有一个分配器来分配大小不能用size_t 表示的对象?换句话说,size_type 可以大于size_t 吗?
【问题讨论】:
带有std::allocator 的标准容器有其size_type defined as std::size_t。但是,是否可以有一个分配器来分配大小不能用size_t 表示的对象?换句话说,size_type 可以大于size_t 吗?
【问题讨论】:
是的,这在某些情况下可能很有用。
假设您有一个程序希望访问比虚拟内存容量更大的存储空间。通过创建引用内存映射存储的分配器并在间接pointer 对象时根据需要对其进行映射,您可以访问任意大量内存。
这仍然符合 18.2:6,因为 size_t 被定义为足够大以包含任何对象的大小,但 17.6.3.5:2 表 28 将 size_type 定义为包含最大对象的大小在分配模型中,它不必是 C++ 内存模型中的实际对象。
请注意,17.6.3.5:2 表 28 中的要求并不构成分配多个对象应导致数组的要求;对于allocate(n),要求是:
内存分配给
T类型的n对象
对于deallocate,断言是:
该区域内的所有
nT对象p指向的应为 在此调用之前销毁。
注意区域,而不是数组。还有一点是17.6.3.5:4:
X::pointer、X::const_pointer、X::void_pointer和X::const_void_pointer类型应满足 NullablePointer (17.6.3.3) 的要求。没有构造函数,比较运算符,复制操作, 这些类型的移动操作或交换操作应通过异常退出。X::pointer和X::const_pointer也应满足随机访问迭代器 (24.2) 的要求。
这里没有要求(&*p) + n 必须与p + n 相同。
在另一个模型中可表达的模型包含在外部模型中不可表达的对象是完全合法的;例如,数学逻辑中的非标准模型。
【讨论】:
size_t 是应用sizeof 得到的无符号整数的类型。
sizeof 应该返回作为其参数的类型(或表达式类型)的大小。如果是数组,它应该返回整个数组的大小。
这意味着:
不能有任何结构或联合大于size_t 可以表示的值。
任何数组都不能大于size_t 可以表示的值。
换句话说,如果某些东西适合您可以访问的最大连续内存块,那么它的大小必须适合 size_t(在不可移植但易于直观理解的术语中,这意味着在大多数系统上size_t与void* 一样大,可以“测量”整个虚拟地址空间)。
编辑:下一句可能是错误的。见下文
因此的答案是否有可能有一个分配器来分配大小不能用size_t 表示的对象? 是否。
编辑(附录):
我一直在考虑它,上面的我实际上是错误的。我检查了标准,似乎可以设计一个具有完全自定义指针类型的完全自定义分配器,包括对指针、const 指针、void 指针和 const void 指针使用不同的类型。因此,分配器实际上可以具有大于 size_t 的 size_type。
但要做到这一点,您实际上需要定义完全自定义的指针类型以及相应的分配器和分配器特征实例。
我说可能的原因是我仍然有点不清楚size_type是否需要跨越单个对象的大小或多个对象的大小(即数组) 在分配器模型中。我需要调查这个细节(但不是现在,这里是晚餐时间:))
Edit2(新附录):
@larsmans 我想你可能想决定接受什么。这个问题似乎比人们直观地意识到的要复杂一些。我正在再次编辑答案,因为我的想法绝对不仅仅是评论(无论是内容还是大小)。
重新编辑(正如 cmets 中指出的,接下来的两段不正确):
首先size_type 只是一个名字。当然,您可以定义一个容器并向其添加一个 size_type,以您希望的任何含义。您的 size_type 可以是浮点数,也可以是字符串。
在标准库容器中所说的size_type 在容器中定义只是为了使其易于访问。实际上,它应该与该容器的分配器的size_type 相同(分配器的size_type 应该是该分配器的allotator_traits 的size_type)。
因此,我们将假设容器的size_type,即使是您定义的容器,也遵循“按惯例”相同的逻辑。 @BenVoight 以“正如@AnalogFile 解释的那样,分配的内存不能大于 size_t。因此,从分配器继承其 size_type 的容器的 size_type 不能大于 size_t。”开始他的回答。事实上,我们现在规定,如果一个容器有一个size_type,那么它来自分配器(他说继承,但这当然不是类继承的常识)。
但是,他可能 100% 正确,也可能不会 100% 正确,即 size_type(即使它来自分配器)必然被限制为 size_t。真正的问题是:分配器(和相应的特征)可以定义一个大于size_t 的size_type 吗?
@BenVoight 和@ecatmur 都建议使用后备存储是文件的用例。但是,如果后备存储是仅用于内容的文件,并且您在内存中有一些引用该内容的内容(我们称其为“句柄”),那么您实际上是在做一个包含句柄的容器。句柄将是某个类的实例,它将实际数据存储在文件中,并且只将检索该数据所需的任何内容保存在内存中,但这与容器无关:容器将存储句柄,而这些句柄在内存中我们仍然在“正常”地址空间中,所以我的初始响应仍然有效。
但是,还有另一种情况。您没有分配句柄,您实际上是在文件(或数据库)中存储内容,并且您的分配器(和相关特征)定义直接管理该后备存储的指针、常量指针、无效指针、常量无效指针等类型。在这种情况下,当然他们还需要定义size_type(替换size_t)和difference_type(替换ptrdiff_t)来匹配。
当size_t 已经与提供的原始整数类型的最大实现一样大时,将size_type(和difference_type)定义为大于size_t 的直接困难是(如果没有,则没有困难)是与他们需要是integer types 的事实有关。
根据您如何解释标准,这可能是不可能的(因为根据标准integer types 是标准中定义的类型加上实现提供的extended integer types)或可能的(如果您解释它这样您可以自己提供extended integer type),只要您可以编写一个行为完全类似于原始类型的类。这在过去是不可能的(重载规则确实使原始类型总是与用户定义的类型区分开来),但我不是 100% 与 C++11 保持同步,这可能(或可能不会改变)。
但是也有间接的困难。您不仅需要为size_type 提供合适的整数类型。您还需要提供分配器接口的其余部分。
我一直在考虑它,我看到的一个问题是根据 17.6.3.5 实施 *p。在 *p 语法中,p 是由分配器特征键入的 pointer。当然我们可以写一个类,定义一个operator*(nullary 方法版本,做指针解引用)。并且有人可能认为这可以通过“分页”文件的相关部分来轻松完成(正如@ecatmur 所建议的那样)。但是有一个问题:*p 必须是该对象的T&。因此,对象本身必须适合内存,更重要的是,由于您可以执行T &ref = *p 并无限期地持有该引用,一旦您将数据分页,您将不再被允许将其分页。这意味着除非整个后备存储也可以加载到内存中,否则可能无法有效地正确实现这样的分配器。
这些是我早期的观察,似乎确实证实了我的第一印象,即真正的答案是否定的:没有实际的方法可以做到这一点。
但是,如您所见,事情远比直觉所暗示的要复杂得多。可能需要相当长的时间才能找到明确的答案(我可能会也可能不会继续研究该主题)。
暂时我只想说:这似乎是不可能的。相反的陈述仅在不完全基于直觉的情况下才可接受:邮政编码并让人们辩论您的代码是否完全符合 17.6.3.5 以及您的 size_type(即使 @ 也应大于 size_t 987654366@与最大原始整数类型一样大)可以认为是整数类型。
【讨论】:
sizeof(size_t) 是 8,sizeof(long)、sizeof(long long) 和 sizeof(void*) 也是。事实上,任何 64 位系统都会有sizeof(size_t),即 8。而且没有多少系统有 128 位(或任何高于 64 位)的long long。如果您有 32 位 size_t,那么您使用的是 32 位系统(老实说,感觉有点过时,因为英特尔的最后一个非 64 位处理器是在 8 年前发布的)。
是和不是。
正如@AnalogFile 解释的那样,分配的内存不能大于size_t。因此,从分配器继承其size_type 的容器的size_type 不能大于size_t。
但是,您可以设计一个容器类型来表示不完全存储在可寻址内存中的集合。例如,成员可能在磁盘上或数据库中。它们甚至可以动态计算,例如一个斐波那契数列,而且从不存储在任何地方。在这种情况下,size_type 很容易大于size_t。
【讨论】:
我确定它隐藏在某处的标准中,但我看到的对 size_type 的最佳描述来自 SGI-STL 文档。正如我所说,我确信它在标准中,如果有人能指出它,一定要这样做。
根据 SGI,容器的 size_type 是:
无符号整数类型,可以表示任何非负值 容器的距离类型
它没有声称必须是除此之外的任何东西。理论上,您可以定义一个使用 uint64_t、unsigned char 以及介于两者之间的任何内容的容器。它引用容器的 distance_type 是我觉得有趣的部分,因为...
distance_type:有符号整数类型,用于表示距离 在容器的两个迭代器之间。此类型必须相同 作为迭代器的距离类型。
虽然这并不能真正回答问题,但是看看 size_type 和 size_t 如何不同(或可以)是很有趣的。关于您的问题,请参阅(并投票)@AnalogFile 的答案,因为我认为它是正确的。
【讨论】:
size_t,但是一个使用 64 位文件系统的磁盘分配器,这意味着 distance_type 和 size_type 将是 64 位偏移量。跨度>
从§18.2/6
size_t类型是实现定义的无符号整数类型,其大小足以包含任何对象的字节大小。
因此,如果您可以分配一个大小不能由 size_t 表示的对象,那么它会导致实现不一致。
【讨论】:
N 的对象,以便它自己的size() 函数返回N。想想std::list。因此,容器的大小类型与用于单个对象大小的类型没有任何内在的关系,除了在实践中它们通常都是内存空间的大小。
std::list 可能会要求其分配器分配与所包含对象大小一样大的块。也许我的回答也没有说清楚,但我说的是向分配器发出的单个分配请求的大小限制。
SIZE_MAX 的对象。我不知道我们在谈论哪个size_type。但正如 ecatmur 所解释的,当分配器为“N 个事物分配足够的内存”时,它们分配的内存不一定是一个对象,尽管 N 个事物中的每一个都是。
要添加到“标准”答案,还请注意 stxxl 项目,该项目应该能够使用磁盘存储(可能通过扩展,网络存储)处理数 TB 的数据。例如,请参见 header of vector,将 size_type(line 731 和 line 742)定义为 uint64。
这是一个使用比内存容量更大的容器的具体示例,或者甚至系统的整数都可以处理。
【讨论】:
不一定。
我假设 size_type 是指大多数 STL 容器中的 typedef?
如果是这样,那只是因为 size_type 被添加到所有容器中 只使用 size_t 意味着 STL 保留制作的权利 size_type 他们喜欢的任何类型。 (默认情况下,在我知道的所有实现中 size_type 是 size_t 的 typedef)。
【讨论】: