我使用大量用 c++ 编写的计算代码,并考虑到了高性能和低内存开销。它大量使用 STL 容器(主要是 std::vector
),并且几乎在每个函数中都对这些容器进行迭代。
迭代代码如下所示:
for (int i = 0; i < things.size(); ++i)
{
// ...
}
但它会产生有符号/无符号不匹配警告(Visual Studio 中的 C4018)。
用一些 unsigned
类型替换 int
是一个问题,因为我们经常使用 OpenMP
pragma,它要求计数器是 int
。
我即将压制(数百个)警告,但恐怕我错过了一些优雅的解决问题的方法。
在迭代器上。我认为迭代器在适当的地方应用时很棒。我正在使用的代码从不将随机访问容器更改为 std::list
或其他内容(因此使用 int i
进行迭代已经与容器无关),并且将始终需要当前索引。您需要输入的所有额外代码(迭代器本身和索引)只会使事情复杂化并混淆底层代码的简单性。
int
。
int
和 std::vector<T>::size_type
的大小和符号也可能不同。例如,在 LLP64 系统(如 64 位 Windows)上,sizeof(int) == 4
但 sizeof(std::vector<T>::size_type) == 8
。
这一切都在您的 things.size()
类型中。它不是 int
,而是 size_t
(它存在于 C++ 中,而不存在于 C 中),它等于某些“通常的”无符号类型,即 x86_32 的 unsigned int
。
运算符 "less" (<) 不能应用于不同符号的两个操作数。只是没有这样的操作码,标准也没有指定编译器是否可以进行隐式符号转换。所以它只是将有符号数视为无符号数并发出警告。
写成这样是正确的
for (size_t i = 0; i < things.size(); ++i) { /**/ }
甚至更快
for (size_t i = 0, ilen = things.size(); i < ilen; ++i) { /**/ }
理想情况下,我会使用这样的构造:
for (std::vector<your_type>::const_iterator i = things.begin(); i != things.end(); ++i)
{
// if you ever need the distance, you may call std::distance
// it won't cause any overhead because the compiler will likely optimize the call
size_t distance = std::distance(things.begin(), i);
}
这具有一个巧妙的优势,即您的代码突然变得与容器无关。
关于您的问题,如果您使用的某个库要求您在 unsigned int
更适合的地方使用 int
,那么他们的 API 就会很混乱。无论如何,如果您确定那些 int
总是积极的,您可以这样做:
int int_distance = static_cast<int>(distance);
这将清楚地说明你对编译器的意图:它不会再用警告来打扰你了。
static_cast<int>(things.size())
可能是解决方案。
#pragma warning(push) #pragma warning(disable: 4018) /* ... function */ #pragma warning(pop)
),而不是使用不必要的强制转换。 (演员隐藏了合法的错误,m'kay?;))
如果您不能/不会使用迭代器,并且如果您不能/不会使用 std::size_t
作为循环索引,请创建一个 .size()
到 int
的转换函数来记录假设并显式执行转换使编译器警告静音。
#include <cassert>
#include <cstddef>
#include <limits>
// When using int loop indexes, use size_as_int(container) instead of
// container.size() in order to document the inherent assumption that the size
// of the container can be represented by an int.
template <typename ContainerType>
/* constexpr */ int size_as_int(const ContainerType &c) {
const auto size = c.size(); // if no auto, use `typename ContainerType::size_type`
assert(size <= static_cast<std::size_t>(std::numeric_limits<int>::max()));
return static_cast<int>(size);
}
然后你写你的循环是这样的:
for (int i = 0; i < size_as_int(things); ++i) { ... }
这个函数模板的实例化几乎肯定会被内联。在调试版本中,将检查假设。在发布版本中,它不会,并且代码将与您直接调用 size() 一样快。这两个版本都不会产生编译器警告,而且它只是对惯用循环的轻微修改。
如果您还想在发布版本中捕获假设失败,您可以将断言替换为抛出类似 std::out_of_range("container size exceeds range of int")
的 if 语句。
请注意,这解决了有符号/无符号比较以及潜在的 sizeof(int)
!= sizeof(Container::size_type)
问题。您可以启用所有警告并使用它们来捕获代码其他部分中的真正错误。
您可以使用:
size_t 类型,删除警告消息迭代器+距离(就像是第一个提示)只有迭代器函数对象
例如:
// simple class who output his value
class ConsoleOutput
{
public:
ConsoleOutput(int value):m_value(value) { }
int Value() const { return m_value; }
private:
int m_value;
};
// functional object
class Predicat
{
public:
void operator()(ConsoleOutput const& item)
{
std::cout << item.Value() << std::endl;
}
};
void main()
{
// fill list
std::vector<ConsoleOutput> list;
list.push_back(ConsoleOutput(1));
list.push_back(ConsoleOutput(8));
// 1) using size_t
for (size_t i = 0; i < list.size(); ++i)
{
std::cout << list.at(i).Value() << std::endl;
}
// 2) iterators + distance, for std::distance only non const iterators
std::vector<ConsoleOutput>::iterator itDistance = list.begin(), endDistance = list.end();
for ( ; itDistance != endDistance; ++itDistance)
{
// int or size_t
int const position = static_cast<int>(std::distance(list.begin(), itDistance));
std::cout << list.at(position).Value() << std::endl;
}
// 3) iterators
std::vector<ConsoleOutput>::const_iterator it = list.begin(), end = list.end();
for ( ; it != end; ++it)
{
std::cout << (*it).Value() << std::endl;
}
// 4) functional objects
std::for_each(list.begin(), list.end(), Predicat());
}
C++20 现在有 std::cmp_less
在 c++20 中,我们有标准的 constexpr
函数
std::cmp_equal
std::cmp_not_equal
std::cmp_less
std::cmp_greater
std::cmp_less_equal
std::cmp_greater_equal
在 <utility>
标头中添加,正是针对这种情况。
比较两个整数 t 和 u 的值。与内置比较运算符不同,带负号的整数总是比较小于(且不等于)无符号整数:比较对有损整数转换是安全的。
这意味着,如果(由于某些连线原因)必须使用 i
作为 int
eger、循环,并且需要与无符号整数进行比较,则可以这样做:
#include <utility> // std::cmp_less
for (int i = 0; std::cmp_less(i, things.size()); ++i)
{
// ...
}
这也涵盖了如果我们错误地将static_cast
-1
(即 int
)unsigned int
的情况。这意味着,以下内容不会给您错误:
static_assert(1u < -1);
但是 std::cmp_less
的使用会
static_assert(std::cmp_less(1u, -1)); // error
我还可以为 C++11 提出以下解决方案。
for (auto p = 0U; p < sys.size(); p++) {
}
(对于自动 p = 0,C++ 不够聪明,所以我必须把 p = 0U ....)
for (auto thing : vector_of_things)
。
size()
返回一个大于 unsigned int 的类型,这将无济于事,这非常常见。
我会给你一个更好的主意
for(decltype(things.size()) i = 0; i < things.size(); i++){
//...
}
decltype
是
检查实体的声明类型或表达式的类型和值类别。
因此,它推断出 things.size()
的类型,并且 i
将是与 things.size()
相同的类型。因此,i < things.size()
将在没有任何警告的情况下执行
我有一个类似的问题。使用 size_t 不起作用。我尝试了另一个对我有用的。 (如下)
for(int i = things.size()-1;i>=0;i--)
{
//...
}
我会做
int pnSize = primeNumber.size();
for (int i = 0; i < pnSize; i++)
cout << primeNumber[i] << ' ';
size_t
。它是std::vector< THING >::size_type
。std::size_t
和std::vector<T>::size_type
提供不同的底层类型。