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

ten*_*dim 6 c++ vector multiplatform long-integer

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

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

我遇到了看似简单的任务的问题,例如std::minstd::max,这可能更系统化.请考虑以下代码:

uint32_t getmax()
{
    return _vecContainer.size();
}
Run Code Online (Sandbox Code Playgroud)

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

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);
}
Run Code Online (Sandbox Code Playgroud)

再次,简单.

问题:

uint32_t foo(const uint32_t &r_row)
{
    return std::min(r_row, _vecContainer.size());
}
Run Code Online (Sandbox Code Playgroud)

这给了我错误,例如:

/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)
Run Code Online (Sandbox Code Playgroud)

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

解决方案是做老式的weay:

#define MIN_M(a,b) a < b ? a : b
return MIN_M(r_row, _vecContainer.size());
Run Code Online (Sandbox Code Playgroud)

花花公子.但系统性问题仍然存在:在规划多平台支持时,您如何处理这样的实例?我可以使用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
Run Code Online (Sandbox Code Playgroud)

有什么建议?

编辑:我读过size_t签名的地方,我已经纠正了.到目前为止看起来这是我自己设计的限制(例如32位数与使用std :: vector :: size_type和/或size_t).

vso*_*tco 6

解决这个问题的一种方法是使用

std::vector<Type>::size_type
Run Code Online (Sandbox Code Playgroud)

作为函数参数/返回的基础类型,或者auto如果使用C++ 14则返回.

  • @tendim 那么你有点不走运,因为在这种情况下设计并不是最好的。你唯一能做的就是强制转换。在使用泛型类型时,最好也使用与平台无关的数据类型,不要只使用 `uint32_t`。 (2认同)

Adr*_*thy 5

一组花絮形式的答案:

  1. 您可以在使用函数模板(如std::min<T>. 例如:std::min<std::uint32_t>(4, my_vec.size());

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

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

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

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

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

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