7个回答

28

是的,在某些情况下这可能会很有用。

假设您有一个程序希望访问比虚拟内存中可容纳的更多的存储空间。通过创建引用内存映射存储的分配器,并在间接指针对象时根据需要进行映射,您可以访问任意大量的内存。

这仍然符合18.2:6,因为size_t定义为足够大以包含任何对象的大小,但17.6.3.5:2表28定义size_type包含分配模型中最大对象的大小,该对象不必是C++内存模型中的实际对象。

请注意,17.6.3.5:2表28中的要求并不构成多个对象分配应导致数组的要求;对于allocate(n),要求是:

为类型Tn个对象分配内存

对于deallocate,断言是:

在此调用之前,指向p的区域中的所有nT对象都将被销毁。

请注意区域而不是数组。 另一个要点是17.6.3.5:4:

X::pointerX::const_pointerX::void_pointerX::const_void_pointer类型应满足NullablePointer(17.6.3.3)的要求。这些类型上没有构造函数、比较运算符、复制操作、移动操作或交换操作应通过异常退出。X::pointerX::const_pointer还应满足随机访问迭代器(24.2)的要求。

这里没有要求(&*p) + n应与p + n相同。

在另一个模型中可表达的模型中包含不能在外部模型中表示的对象是完全合法的;例如,数学逻辑中的非标准模型。


1
引用的文本已经过时。在C++14和C++17之间,LWG2384删除了“area”措辞。在C++17和C++20之间,P0593R6更改了allocate的措辞为“为n T数组分配内存并创建这样的对象,但不构造数组元素”。 - FrankHB

21

size_t是应用sizeof操作得到的无符号整数类型。

sizeof应返回其参数所表示的类型(或表达式类型)的大小。对于数组,它应该返回整个数组的大小。

这意味着:

  • 不能有任何大于 size_t 能够表示的结构体或联合体。

  • 不能有任何大于 size_t 能够表示的数组。

换句话说,如果某些东西适合你可以访问的最大连续内存块,那么其大小必须适合 size_t(以不具可移植性但易于理解的直观术语来说,这意味着在大多数系统上,size_tvoid* 一样大,并且可以"测量"您的虚拟地址空间的全部内容)。

编辑:下面的句子可能是错误的。请参见以下内容。

因此,回答“是否可能有一个分配器,分配对象的大小无法由 size_t 表示?”是否定的。

编辑(补充):

我一直在考虑这个问题,上面的回答可能是错误的。我检查了标准,似乎可以设计一个完全自定义的分配器,使用完全自定义的指针类型,包括使用不同类型的指针、const 指针、void 指针和const void指针。因此,实际上可以有一个 size_type 大于 size_t 的分配器。

但要这样做,您需要定义完全自定义的指针类型以及相应的分配器和分配器特性实例。

我说“可能”是因为我还不太清楚 size_type 是否需要跨越分配器模型中单个对象的大小或多个对象(即数组)的大小。我将需要调查这个细节(但现在不行,这里是晚餐时间 :))

编辑2(新补充):

@larsmans 我认为你可能需要决定接受什么。这个问题似乎比人们直觉上意识到的要复杂一些。我再次编辑答案,因为我的想法明显比评论更详尽(无论是内容还是大小)。

重新编辑(正如评论中指出的那样,下面的两段话是不正确的):

首先,size_type 只是一个名称。当然,您可以定义一个容器并添加一个 size_type,并赋予其任何您希望的含义。您的 size_type 可以是浮点数,也可以是字符串等。

在标准库容器中,size_type仅定义在容器中以方便访问,实际上应与该容器的分配器的size_type相同(而分配器的size_type应为该分配器的allocator_traitssize_type)。因此,我们从现在开始假设容器的size_type,即使是您自己定义的容器,也遵循相同的约定。@BenVoight以“As @AnalogFile解释,没有分配的内存可以大于size_t。因此,继承其size_type的容器不能具有大于size_tsize_type。”开头回答了这个问题。实际上,我们现在规定如果容器有一个size_type,那么它来自分配器(他说继承,但这当然不是类继承的常见意义)。
然而,他可能也可能不完全正确,即使size_type来自分配器,它是否一定受限于size_t。问题实际上是:分配器(和相应的traits)是否可以定义一个大于size_tsize_type
@BenVoight和@ecatmur都提出了一个使用案例,其中后备存储是一个文件。但是,如果后备存储仅用于内容,并且您有一些引用该内容的内存中的东西(让我们称之为“handle”),那么您实际上正在做一个包含句柄的容器。句柄将是某个类的实例,该类在文件上存储实际数据并仅保留它需要检索该数据的任何内容,但这与容器无关:容器将存储句柄,而这些句柄在内存中,我们仍然处于“正常”的地址空间中,因此我的初始回答仍然有效。
然而还有另一种情况。您不是分配句柄,实际上将东西存储在文件(或数据库)中,您的分配器(及其相关traits)定义指针、const指针、void指针、const void指针等类型,直接管理该后备存储。在这种情况下,当然,它们也需要定义size_type(替换size_t)和difference_type(替换ptrdiff_t)以匹配。
定义size_type(和difference_type)大于size_t的直接困难是当size_t已经作为最大的实现提供的原始整数类型时(如果不是,那么就没有困难),它们需要是整数类型。根据标准的解释方式,这可能是不可能的(因为根据标准,整数类型是指标准中定义的类型加上由实现提供的扩展整数类型),或者是可能的(如果您将其解释为可以自己提供扩展整数类型),只要您能编写一个像原始类型一样行为的类。在旧时代是不可能的(重载规则使原始类型始终可与用户定义类型区分),但我对C++11不是100%熟悉,这可能已经改变了。
然而也存在间接的困难。您不仅需要为size_type提供适当的整数类型。您还需要提供其余的分配器接口。
我稍微想了一下,其中一个问题是如何按照17.6.3.5实现*p。在该*p语法中,p是由分配器特征类型化的指针。当然,我们可以编写一个类并定义一个operator*(无参数版本,执行指针解引用)。有人可能认为,通过“调入”文件的相关部分,可以轻松地完成此操作(正如@ecatmur所建议的那样)。然而,存在一个问题:对于该对象,*p必须是T&。因此,该对象本身必须适合内存,并且更重要的是,由于您可能会执行T&ref = *p并且持有该引用时间不限,一旦数据被调入,您将永远不允许将其调出。这意味着,实际上可能没有办法正确地实现这样的分配器,除非整个后备存储区也可以加载到内存中。
这些是我早期的观察结果,并似乎实际上证实了我的第一印象:真正的答案是否定的,没有实际可行的方法来做到这一点。
然而,正如您所见,事情要比单纯的直觉复杂得多。找到一个最终答案可能需要相当长的时间(我可能会或可能不会进一步研究该主题)。
目前,我只能说:看起来似乎不可能。否则,只有在它们不仅仅基于直觉时,才可以接受反驳的声明:发布代码并让人们辩论您的代码是否完全符合17.6.3.5以及您的size_type(即使size_t与最大的原始整数类型一样大,您的size_type也应更大)。

如果您的自定义分配器是用于具有大量内存的云的分配器,会怎样呢? - orlp
size_t被认为是最大整数类型一样大是一个很大的假设。更不用说现在已经过时的分段内存模型了,那么对于所有这些具有32位size_t和64位long long的系统呢? - Cubbi
在我的系统上,sizeof(size_t)是8,sizeof(long)sizeof(long long)sizeof(void*)也是8。实际上,任何64位系统都会有sizeof(size_t)为8。而且很少有系统的long long超过64位(或者超过128位)。如果你的size_t是32位,那么你就是在一个32位的系统上(说实话,这感觉有点过时,因为英特尔最后一个非64位处理器发布已经有大约8年了)。 - Analog File
"你的size_type可以是浮点数,字符串或其他任何类型" - 这不可能是真的。标准中的容器要求确实指定它应该是无符号整数类型,分配器也是如此。 - Fred Foo
至于32位系统,我刚买了一个 树莓派,所以它们还没有死 :) - Fred Foo
@larsmans 您是正确的,我一直在关注分配器并忘记了检查容器要求。我认为我最终会发布一个单独的答案(将此保留为历史参考),以解释为什么我认为不可能同时使用分配器表示内存映射文件,并且也不可能有一个不基于分配器但使用内存映射文件进行存储的容器。但我需要对标准进行更多的分析。 - Analog File

15

是和不是。

@AnalogFile解释说,没有分配的内存可以比size_t更大。因此,从allocator继承其size_type的容器不能具有比size_t更大的size_type

然而,您可以设计一种容器类型,它表示一个不完全存储在可寻址内存中的集合。例如,成员可能存储在磁盘或数据库中。它们甚至可以动态计算,例如斐波那契数列,并且根本不存储任何地方。在这种情况下,size_type可以很容易地比size_t更大。


3
通过更加灵活的定义,可以存在一个抽象,将多个 size_t 大小的内存块拼接在一起,例如在使用 Intel 的 PAE 并且超过 4GB 内存的 32 位 Intel 系统上可能会出现这种情况。 - Charles Burns
@Charles:我不明白这怎么会“更灵活”。这与“数据存储在磁盘上,通过mmap执行磁盘I/O”是一样的情况。但我会添加“可寻址”的词来澄清,因为磁盘也是一种类型的内存。 - Ben Voigt
@BenVoigt:我指的是“比size_t大的对象”的“更灵活”的定义,更多地是作为学术思想而非实用建议的建议。我认为你的原始帖子很好。 :) - Charles Burns

5

我确信它在标准中被隐藏了起来,但我见过的最好的size_type描述来自SGI-STL文档。正如我所说的,我相信它在标准中存在,如果有人能指出来,那就请指出来。

根据SGI的说法,容器的size_type是:

一个无符号整数类型,可以表示容器的距离类型的任何非负值

它并没有声称除此之外还必须是什么。理论上,您可以定义一个使用uint64_t、unsigned char和介于两者之间的任何其他内容的容器。它引用容器的distance_type这一部分让我觉得很有趣,因为...

distance_type:用于表示容器迭代器之间距离的有符号整数类型。此类型必须与迭代器的距离类型相同。

虽然这并没有真正回答问题,但看到size_type和size_t之间的区别(或可能性)还是很有趣的。关于您的问题,请参见(并点赞)@AnalogFile的答案,因为我认为它是正确的。


+1,标准中大致相同的文本出现了。我想你可以拥有一个32位的内存模型/size_t,但是使用64位文件系统的磁盘分配器,这意味着distance_typesize_type将是64位偏移量。 - Fred Foo

3

来自§18.2/6

size_t类型是一个实现定义的无符号整数类型,它足够大以包含任何对象的字节大小。

因此,如果您可以分配一个大小无法由size_t表示的对象,则会使实现不符合规范。


一个容器不必分配大小为N的对象,以便其自己的size()函数返回N。想想std::list。因此,容器的大小类型与用于单个对象大小的类型之间没有固有的关系,除了实际上它们通常都是内存空间的大小。 - Steve Jessop
@SteveJessop 但是他并不是在问容器的 size_type,问题是关于容器使用的 allocator 的 size_type。std::list 可能会要求其 allocator 分配与所包含对象大小相同的块。也许我的答案也没有很清楚地表达这一点,但我是在谈论分配器对单个分配请求的大小限制。 - Praetorian
不错,您说得对,分配器无法分配比SIZE_MAX大的对象。 我忘记了我们正在讨论哪种size_type。但是正如ecatmur所解释的那样,当分配器分配“足够多的N个东西”的内存时,它们分配的内存不一定是对象,尽管每个N个东西都是对象。 - Steve Jessop
具有讽刺意味的是,我通过注意到libstdc++的std::list在使用C++11 ABI时实际上使用size_t作为节点计数(因此存在错误的max_size())来发现了这个问题... - FrankHB

1
除了“标准”答案之外,还要注意stxxl项目,该项目应该能够使用磁盘存储(或许通过扩展,网络存储)处理数千兆字节的数据。例如,请参见vector的头文件,查看size_type第731行第742行)的定义为uint64。
这是使用比内存更大的容器的具体示例,甚至超出了系统整数的处理范围。

虽然我同意答案是“是”,但 stxxl::vector 似乎没有使用标准兼容的分配器,因此它的 size_type 不是 OP 的 allocator::size_type。不过这是一个很好的使用案例。 - Cubbi
@Cubbi 谢谢。OP基本上在一个问题中问了两个问题。一个关于size_type,另一个关于size_type与分配器有关。这是关于STL中的size_type,不一定是关于其在分配器中的使用。Ben Voigt的答案是一个很好的例子,可以给出补充回答。 - Realz Slaw

0

不一定。

我猜你说的 size_type 是指大多数 STL 容器内部的 typedef?

如果是这样,那么仅仅因为在所有容器中都添加了 size_type 而不是只使用 size_t,这意味着 STL 保留了将 size_type 设为任何他们喜欢的类型的权利。(默认情况下,在我所知道的所有实现中,size_type 都是 size_t 的 typedef)。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接