如何处理"签名/未签名的不匹配"警告(C4018)?

And*_*w T 71 c++ refactoring

我使用大量用C++编写的计算代码,考虑到高性能和低内存开销.它大多使用STL容器vector,并且几乎在每个函数中遍历那些容器.

迭代代码如下所示:

for (int i = 0; i < things.size(); ++i)
{
    // ...
}
Run Code Online (Sandbox Code Playgroud)

但它会产生签名/未签名的不匹配警告(Visual Studio中的C4018).

替换int某种unsigned类型是一个问题,因为我们经常使用OpenMP pragma,它需要计数器int.

我即将压制(数百个)警告,但我担心我错过了一些优雅的解决方案.

在迭代器上.我认为在适当的地方应用迭代器很棒.我正在使用的代码永远不会将随机访问容器更改为list某种东西(因此迭代int i已经是容器不可知),并且总是需要当前索引.您需要键入的所有其他代码(迭代器本身和索引)只会使问题复杂化并模糊底层代码的简单性.

小智 54

这一切都属于你的things.size()类型.它不是int,但size_t(它存在于C++中,而不是C中)等于某些"通常"的无符号类型,即unsigned intx86_32.

运算符"less"(<)不能应用于两个不同符号的操作数.没有这样的操作码,并且标准没有指定,编译器是否可以进行隐式符号转换.所以它只是将签名号码视为未签名并发出警告.

写它是正确的

for (size_t i = 0; i < things.size(); ++i) { /**/ }
Run Code Online (Sandbox Code Playgroud)

甚至更快

for (size_t i = 0, ilen = things.size(); i < ilen; ++i) { /**/ }
Run Code Online (Sandbox Code Playgroud)

  • -1不,它不是`size_t`.它是`std :: vector <THING> :: size_type`. (16认同)
  • @Raedwald:虽然你在技术上是正确的,但很难想象符合标准的实现如何最终得到`std :: size_t`和`std :: vector <T> :: size_type`的不同底层类型. (7认同)
  • 为什么++我被认为更好?for for循环是不是"没有"差异? (3认同)
  • @ShoaibHaider,对于不使用返回值的原语来说,这根本没有关系。但是,对于*自定义类型*(运算符重载的地方),后期增量几乎总是效率较低(因为它必须在增量之前复制对象)。编译器无法(必需)针对自定义类型进行优化。因此,唯一的优势是(原语与自定义类型的)一致性。 (2认同)
  • @AdrianMcCarthy `std::vector&lt;T&gt;::size_type` 取决于使用的分配器。如果使用默认分配器(`std::allocator&lt;T&gt;`),则`size_type == std::size_t`。对于自定义分配器,“size_type”可能不是“std::size_t”。 (2认同)
  • @zenith:是的,你是对的.我的声明仅适用于默认分配器.自定义分配器可能使用std :: size_t以外的东西,但我相信它仍然必须是无符号整数类型,它可能不能代表比std :: size_t更大的范围,所以使用std仍然是安全的:: size_t作为循环索引的类型. (2认同)

ere*_*eOn 13

理想情况下,我会使用这样的结构:

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

这有一个很好的优势,你的代码突然变得容器不可知.

而对于你的问题,如果一些你使用的库要求您使用int其中一个unsigned int会更适合,他们的API是凌乱.无论如何,如果你确定那些int总是积极的,你可能会这样做:

int int_distance = static_cast<int>(distance);
Run Code Online (Sandbox Code Playgroud)

这将明确指出您对编译器的意图:它不会再向您发出警告.


Adr*_*thy 7

如果你不能/不会使用迭代器,如果你不能/不会使用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);
}
Run Code Online (Sandbox Code Playgroud)

然后你写这样的循环:

for (int i = 0; i < size_as_int(things); ++i) { ... }
Run Code Online (Sandbox Code Playgroud)

几乎肯定会内联此函数模板的实例化.在调试版本中,将检查该假设.在发布版本中,它不会,并且代码将像您直接调用size()一样快.这两个版本都不会产生编译器警告,并且只是对惯用循环的轻微修改.

如果你想在发布版本中捕获假设失败,你可以用一个抛出类似的if语句替换断言std::out_of_range("container size exceeds range of int").

请注意,这解决了有符号/无符号比较以及潜在的sizeof(int)!= sizeof(Container::size_type)问题.您可以启用所有警告并使用它们来捕获代码其他部分中的实际错误.


JeJ*_*eJo 7

C++20现在有std::cmp_less

中,我们有标准 constexpr函数

std::cmp_equal
std::cmp_not_equal
std::cmp_less
std::cmp_greater
std::cmp_less_equal
std::cmp_greater_equal
Run Code Online (Sandbox Code Playgroud)

添加在<utility>标题中,正是针对这种场景。

t比较两个整数和的值u。与内置比较运算符不同,负符号整数始终比较小于(且不等于)无符号整数:该比较对于有损整数转换是安全的

这意味着,如果(由于某些有线原因)必须使用循环iint并且需要与无符号整数进行比较,可以这样做:

#include <utility> // std::cmp_less

for (int i = 0; std::cmp_less(i, things.size()); ++i)
{
    // ...
}
Run Code Online (Sandbox Code Playgroud)

static_cast如果我们错误地-1(ie int)to ,这也涵盖了这种情况unsigned int。这意味着,以下内容不会给您带来错误:

static_assert(1u < -1);
Run Code Online (Sandbox Code Playgroud)

std::cmp_less但意志的运用

static_assert(std::cmp_less(1u, -1)); // error
Run Code Online (Sandbox Code Playgroud)


小智 6

您可以使用:

  1. size_t类型,用于删除警告消息
  2. 迭代器+距离(就像第一次提示)
  3. 只有迭代器
  4. 功能对象

例如:

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