C++ vector::size_type:有符号与无符号;整数与长

C++ vector::size_type: signed vs unsigned; int vs. long

我一直在通过在不同平台上编译我的应用程序来对其进行一些测试,从 64 位系统到 32 位系统的转变暴露出许多问题。

我大量使用向量、字符串等,因此需要对它们进行计数。但是,我的函数也使用 32 位无符号数,因为在许多情况下我需要显式使用正整数。

我在处理 std::minstd::max 等看似简单的任务时遇到了问题,这些任务可能更系统化。考虑以下代码:

uint32_t getmax()
{
    return _vecContainer.size();
}

看起来很简单:我知道一个向量不能有负数的元素,所以返回一个无符号整数是完全合理的。

void setRowCol(const uint32_t &r_row; const uint32_t &r_col)
{
    myContainer_t mc;
    mc.row = r_row;
    mc.col = r_col;
    _vecContainer.push_back(mc);
}

再一次,够简单了。

问题:

uint32_t foo(const uint32_t &r_row)
{
    return std::min(r_row, _vecContainer.size());
}

这给我错误,例如:

/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../include/c++/v1/algorithm:2589:1: note: candidate template ignored: deduced conflicting types for parameter '_Tp' ('unsigned long' vs. 'unsigned int')
min(const _Tp& __a, const _Tp& __b)

我做了很多挖掘,在一个平台上 vector::size_type 是一个 8 字节的数字。但是,根据设计,我使用的是无符号的 4 字节数字。这可能会导致事情变得古怪,因为您不能将 8 字节数字隐式转换为 4 字节数字。

解决方案是用老式的方法来做:

#define MIN_M(a,b) a < b ? a : b
return MIN_M(r_row, _vecContainer.size());

这很管用。但系统性问题仍然存在:在规划多平台支持时,您如何处理这样的实例?我可以使用 size_t 作为我的标准尺寸,但这增加了其他复杂性(例如,从一个支持 64 位数字的平台转移到另一个支持 32 位数字的平台)。更大的问题是 size_t 未签名,所以我无法更新我的签名:

size_t foo(const size_t &r_row)
// bad, this allows -1 to be passed, which I don't want

有什么建议吗?

编辑:我在某处读到 size_t 已签名,此后我已得到更正。到目前为止,这似乎是我自己设计的局限性(例如,32 位数字与使用 std::vector::size_type and/or size_t)。

处理这个问题的一种方法是使用

std::vector<Type>::size_type

作为函数的基础类型 parameters/returns,或者 auto returns 如果使用 C++14。

一组花絮形式的答案:

  1. 您可以在使用 std::min<T> 等函数模板时显式指定类型,而不是依赖编译器推导类型。例如:std::min<std::uint32_t>(4, my_vec.size());

  2. 打开所有与有符号与无符号比较和隐式缩小转换相关的编译器警告。尽可能使用大括号初始化,因为它将缩小转换视为错误。

  3. 如果你明确想使用像 std::uint32_t 这样的 32 位值,我会尝试找到最少数量的地方来明确转换(即 static_cast) "sizes" 到较小的类型。您不希望在任何地方都进行强制转换,但是如果您在内部使用库容器大小并且希望 API 使用 std::uint32_t,则在 API 边界明确地进行强制转换,以便用户您的 class 永远不必担心自己进行转换。如果您可以将转换保持在几个地方,那么添加 运行 时间检查(即断言)大小实际上没有超出较小类型的范围就变得实用了。

  4. 如果您不关心确切的大小,请使用 std::size_t,这几乎可以肯定与所有标准容器的 std::XXX::size_type 相同。理论上它们可能不同,但实际上并没有发生。在大多数情况下,std::size_t 不如 std::vector::size_type 冗长,因此它是一个很好的折衷方案。

  5. 很多人(包括 C++ 标准委员会的许多人)会告诉您即使对于大小和索引也要避免使用无符号值。我理解并尊重他们的论点,但我认为他们没有足够的说服力来证明与标准库接口的额外摩擦是合理的。不管 std::size_t 是无符号的是否是历史产物,事实是标准库广泛使用无符号大小。如果你使用其他东西,你的代码最终会充满隐式转换,所有这些都是潜在的错误。更糟糕的是,这些隐式转换使得打开编译器警告变得不切实际,因此所有这些潜在的错误仍然相对不可见。 (即使你知道你的大小永远不会超过较小的类型,被迫关闭编译器关于符号和缩小的警告意味着你可能会错过代码中完全不相关的部分的错误。)匹配 [=57= 的类型]s 你尽可能多地使用,断言并在必要时显式转换,并打开所有警告。

  6. 请记住,auto 不是万灵药。 for (auto i = 0; i < my_vec.size(); ++i) ...for (int i ... 一样糟糕。但是,如果您通常更喜欢算法和迭代器而不是原始循环,auto 会让您走得更远。

  7. 对于除法,除非知道分母不为 0,否则绝不能除。同样,对于无符号整数类型,除非知道减数小于或等于原始值,否则绝不能减去.如果你能养成这种习惯,你就可以避免总是使用签名类型的人所关心的错误。