为什么 QVector::size 返回 int?

2021-12-09 00:00:00 qt size c++ qvector qtcore

std::vector::size() 返回一个 size_type,它是无符号的,通常与 size_t 相同,例如在 64 位平台上为 8 个字节.

相比之下,QVector::size() 返回一个 int,即使在 64 位平台上通常也是 4 个字节,并且它是有符号的,这意味着它可以只到 2^32 的一半.

这是为什么?这似乎很不合逻辑,而且在技术上也有限制,虽然您可能永远需要超过 2^32 个元素的可能性也不大,但使用signed int 将这个范围减少了一半,但没有明显的充分理由.也许是为了避免那些懒得将 i 声明为 uint 而不是 int 的人的编译器警告,他们决定让所有容器返回一个大小类型这毫无意义是更好的解决方案吗?原因不可能这么蠢?

解决方案

至少从 Qt 3 开始,这个问题已经讨论过多次,并且 QtCore 维护者表示,不久前 Qt 7 之前不会发生任何变化,如果有的话.

当时进行讨论时,我认为迟早有人会在 Stack Overflow 上提出这个问题……而且可能还会在其他几个论坛和问答中提出.让我们试着揭开这种情况的神秘面纱.

总的来说,您需要了解这里没有更好或更坏,因为 QVector 不是 std::vector 的替代品.后者不做任何写时复制 (COW),而且这是有代价的.基本上,它适用于不同的用例.它主要用于 Qt 应用程序和框架本身,早期最初用于 QWidgets.

size_t 也有它自己的问题,毕竟我将在下面指出.

我不给你解释维护者,我直接引用蒂亚戈来承载官方立场:

<块引用>

有两个原因:

1) 它是有符号的,因为我们需要在 API 的几个地方使用负值:indexOf() 返回 -1 表示未找到值;许多来自"参数可以采用负值来表示从末尾开始计数.所以即使如果我们使用 64 位整数,我们需要它的有符号版本.那就是POSIX ssize_t 或 Qt qintptr.

当您将无符号数隐式转换为无符号数时,这也避免了符号更改警告签名:

-1 + size_t_variable =>警告size_t_variable - 1 =>没有警告

<块引用>

2) 它只是int"以避免与相关的转换警告或丑陋的代码使用大于 int 的整数.

io/qfilesystemiterator_unix.cpp

size_t maxPathName = ::pathconf(nativePath.constData(), _PC_NAME_MAX);if (maxPathName == size_t(-1))

io/qfsfileengine.cpp

if (len <0 || len != qint64(size_t(len))) {

io/qiodevice.cpp

qint64 QIODevice::bytesToWrite() const{返回 qint64(0);}返回 readSoFar ?readSoFar : qint64(-1);

那是来自蒂亚戈的一封电子邮件,然后是还有另一封 在这里你可以找到一些详细的答案:

<块引用>

即使在今天,核心内存超过 4 GB(甚至 2 GB)的软件是一个例外,而不是规则.看的时候请小心一些处理工具的内存大小,因为它们不代表实际内存用法.

无论如何,我们在这里谈论的是有一个单一的容器寻址超过 2 GB 的内存.由于隐式共享 &写时复制Qt 容器的性质,这可能会非常低效.你需要在编写此类代码时要非常小心,以避免触发 COW,从而使您的内存使用量增加一倍或更糟.此外,Qt 容器不处理 OOM情况,所以如果你接近你的内存限制,Qt 容器是使用错误的工具.

我系统上最大的进程是 qtcreator,它也是唯一的一个在 VSZ (4791 MB) 中超过 4 GB 标记的文件.你可以争辩说这是一个表明需要 64 位容器,但您错了:

  • Qt Creator 没有任何需要 64 位大小的容器,它只是需要 64 位指针

  • 它没有使用 4 GB 的内存.那只是 VSZ(映射内存).总数Creator 目前可访问的 RAM 仅为 348.7 MB.

  • 而且它使用了超过 4 GB 的虚拟空间因为它是一个 64 位应用.因果关系与你所想的相反预计.为了证明这一点,我检查了消耗了多少虚拟空间填充:800 MB.32 位应用程序永远不会这样做,那是 19.5%4 GB 上的可寻址空间.

(填充是分配的虚拟空间,但没有任何支持;它只是以便其他内容不会映射到这些页面)

通过蒂亚戈的回答更深入地探讨这个话题,请看:

<块引用><块引用><块引用>

就我个人而言,我很高兴 Qt 集合大小已签名.它似乎对我来说,一个可能在表达式中使用的整数值使用减法未签名(例如 size_t).

无符号整数并不能保证表达式涉及该整数永远不会是负数.它只保证结果将是一场绝对的灾难.

另一方面,C 和 C++ 标准定义了 unsigned 的行为上溢和下溢.

有符号整数不会上溢或下溢.我的意思是,他们这样做是因为类型和 CPU 寄存器的位数有限,但标准说它们别.这意味着编译器将始终优化假设您没有过度 -或下溢它们.

示例:

for (int i = 1; i >= 1; ++i)

<块引用>

这被优化为无限循环,因为有符号整数不会溢出.如果将其更改为无符号,则编译器知道它可能会溢出然后归零.

有些人不喜欢这样:http://gcc.gnu.org/bugzilla/show_bug.cgi?id=30475

std::vector::size() returns a size_type which is unsigned and usually the same as size_t, e.g. it is 8 bytes on 64bit platforms.

In constrast, QVector::size() returns an int which is usually 4 bytes even on 64bit platforms, and at that it is signed, which means it can only go half way to 2^32.

Why is that? This seems quite illogical and also technically limiting, and while it is nor very likely that you may ever need more than 2^32 number of elements, the usage of signed int cuts that range in half for no apparent good reason. Perhaps to avoid compiler warnings for people too lazy to declare i as a uint rather than an int who decided that making all containers return a size type that makes no sense is a better solution? The reason could not possibly be that dumb?

解决方案

This has been discussed several times since Qt 3 at least and the QtCore maintainer expressed that a while ago no change would happen until Qt 7 if it ever does.

When the discussion was going on back then, I thought that someone would bring it up on Stack Overflow sooner or later... and probably on several other forums and Q/A, too. Let us try to demystify the situation.

In general you need to understand that there is no better or worse here as QVector is not a replacement for std::vector. The latter does not do any Copy-On-Write (COW) and that comes with a price. It is meant for a different use case, basically. It is mostly used inside Qt applications and the framework itself, initially for QWidgets in the early times.

size_t has its own issue, too, after all that I will indicate below.

Without me interpreting the maintainer to you, I will just quote Thiago directly to carry the message of the official stance on:

For two reasons:

1) it's signed because we need negative values in several places in the API: indexOf() returns -1 to indicate a value not found; many of the "from" parameters can take negative values to indicate counting from the end. So even if we used 64-bit integers, we'd need the signed version of it. That's the POSIX ssize_t or the Qt qintptr.

This also avoids sign-change warnings when you implicitly convert unsigneds to signed:

-1 + size_t_variable        => warning
size_t_variable - 1     => no warning

2) it's simply "int" to avoid conversion warnings or ugly code related to the use of integers larger than int.

io/qfilesystemiterator_unix.cpp

size_t maxPathName = ::pathconf(nativePath.constData(), _PC_NAME_MAX);
if (maxPathName == size_t(-1))

io/qfsfileengine.cpp

if (len < 0 || len != qint64(size_t(len))) {

io/qiodevice.cpp

qint64 QIODevice::bytesToWrite() const
{
    return qint64(0);
}

return readSoFar ? readSoFar : qint64(-1);

That was one email from Thiago and then there is another where you can find some detailed answer:

Even today, software that has a core memory of more than 4 GB (or even 2 GB) is an exception, rather than the rule. Please be careful when looking at the memory sizes of some process tools, since they do not represent actual memory usage.

In any case, we're talking here about having one single container addressing more than 2 GB of memory. Because of the implicitly shared & copy-on-write nature of the Qt containers, that will probably be highly inefficient. You need to be very careful when writing such code to avoid triggering COW and thus doubling or worse your memory usage. Also, the Qt containers do not handle OOM situations, so if you're anywhere close to your memory limit, Qt containers are the wrong tool to use.

The largest process I have on my system is qtcreator and it's also the only one that crosses the 4 GB mark in VSZ (4791 MB). You could argue that it is an indication that 64-bit containers are required, but you'd be wrong:

  • Qt Creator does not have any container requiring 64-bit sizes, it simply needs 64-bit pointers

  • It is not using 4 GB of memory. That's just VSZ (mapped memory). The total RAM currently accessible to Creator is merely 348.7 MB.

  • And it is using more than 4 GB of virtual space because it is a 64-bit application. The cause-and-effect relationship is the opposite of what you'd expect. As a proof of this, I checked how much virtual space is consumed by padding: 800 MB. A 32-bit application would never do that, that's 19.5% of the addressable space on 4 GB.

(padding is virtual space allocated but not backed by anything; it's only there so that something else doesn't get mapped to those pages)

Going into this topic even further with Thiago's responses, see this:

Personally, I'm VERY happy that Qt collection sizes are signed. It seems nuts to me that an integer value potentially used in an expression using subtraction be unsigned (e.g. size_t).

An integer being unsigned doesn't guarantee that an expression involving that integer will never be negative. It only guarantees that the result will be an absolute disaster.

On the other hand, the C and C++ standards define the behaviour of unsigned overflows and underflows.

Signed integers do not overflow or underflow. I mean, they do because the types and CPU registers have a limited number of bits, but the standards say they don't. That means the compiler will always optimise assuming you don't over- or underflow them.

Example:

for (int i = 1; i >= 1; ++i)

This is optimised to an infinite loop because signed integers do not overflow. If you change it to unsigned, then the compiler knows that it might overflow and come back to zero.

Some people didn't like that: http://gcc.gnu.org/bugzilla/show_bug.cgi?id=30475

相关文章