Как поступать с предупреждениями о несоответствии подписи / без подписи (C4018)?

80

Я работаю с большим количеством вычислительного кода, написанного на C ++, имея в виду высокую производительность и небольшие накладные расходы на память. Он часто использует контейнеры STL (в основном vector) и перебирает эти контейнеры почти в каждой отдельной функции.

Повторяющийся код выглядит так:

for (int i = 0; i < things.size(); ++i)
{
    // ...
}

но выдает предупреждение о несоответствии со знаком / без знака (C4018 в Visual Studio).

Замена intна какой-либо unsignedтип - проблема, потому что мы часто используем прагмы OpenMP, и для этого требуется счетчик int.

Я собираюсь подавить (сотни) предупреждений, но боюсь, что упустил изящное решение проблемы.

Об итераторах . Я считаю, что итераторы хороши, когда применяются в соответствующих местах. Код, с которым я работаю, никогда не изменит контейнеры с произвольным доступом listили что-то в этом роде (поэтому итерация int iуже не зависит от контейнера) и всегда будет нуждаться в текущем индексе. И весь дополнительный код, который вам нужно ввести (сам итератор и индекс), просто усложняет ситуацию и скрывает простоту основного кода.

Эндрю Т
источник
1
Можете ли вы опубликовать пример, в котором прагма OpenMP не позволяет вам использовать беззнаковый тип? В соответствии с этим он должен работать для любого типа интергалов, а не только int.
Билли Онил
4
Я считаю, что этот вопрос лучше для stackoverflow.
bcsanches
1
intа std::vector<T>::size_typeтакже могут быть разными по размеру и подписи. Например, в системе LLP64 (например, 64-битной Windows), sizeof(int) == 4но sizeof(std::vector<T>::size_type) == 8.
Адриан Маккарти
возможный дубликат stackoverflow.com/questions/8188401/…
CinCout,

Ответы:

60

Это все в твоем things.size()вкусе. Это не так int, но size_t(он существует в C ++, а не в C), который соответствует какому-то «обычному» беззнаковому типу, то есть unsigned intдля x86_32.

Оператор «меньше» (<) не может применяться к двум операндам разного знака. Таких кодов операций просто нет, и стандарт не указывает, может ли компилятор выполнять неявное преобразование знака. Таким образом, он просто обрабатывает подписанное число как неподписанное и выдает это предупреждение.

Правильнее было бы написать так

for (size_t i = 0; i < things.size(); ++i) { /**/ }

или даже быстрее

for (size_t i = 0, ilen = things.size(); i < ilen; ++i) { /**/ }
Гигала
источник
17
-1 нет, это не так size_t. Это так std::vector< THING >::size_type.
Raedwald
8
@Raedwald: Хотя вы технически правы, трудно представить, как реализация, соответствующая стандартам, может иметь различные базовые типы для std::size_tи std::vector<T>::size_type.
Адриан Маккарти,
4
почему ++ i считается лучше? В циклах for нет разницы?
Шоаиб
2
@ShoaibHaider, это вообще не имеет значения для примитивов, где возвращаемое значение не используется. Однако для пользовательских типов (где оператор перегружен) пост-инкремент почти всегда менее эффективен (поскольку он должен сделать копию объекта до того, как он будет увеличен). Компиляторы не могут (обязательно) оптимизировать для пользовательских типов. Таким образом, единственным преимуществом является согласованность (примитивов и нестандартных типов).
Кэт
2
@zenith: Да, ты прав. Мое утверждение справедливо только для распределителя по умолчанию. Пользовательский распределитель может использовать что-то другое, кроме std :: size_t, но я считаю, что он все равно должен быть беззнаковым целым типом, и он, вероятно, не может представлять больший диапазон, чем std :: size_t, поэтому по-прежнему безопасно использовать std :: size_t как тип индекса цикла.
Адриан Маккарти,
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);
}

У этого есть преимущество в том, что ваш код внезапно становится независимым от контейнера.

Что касается вашей проблемы, если какая-то используемая вами библиотека требует, чтобы вы использовали ее intтам, где unsigned intона лучше всего подходит, их API беспорядочный. В любом случае, если вы уверены, что intони всегда положительные, вы можете просто сделать:

int int_distance = static_cast<int>(distance);

Что четко укажет компилятору ваше намерение: он больше не будет вызывать у вас предупреждений.

ereOn
источник
1
Мне всегда нужна дистанция. Может static_cast<int>(things.size())быть, решения могут быть, если других нет.
Эндрю Т.
@Andrew: Если вы все же решите подавить предупреждение, лучшим способом, вероятно, будет использование специфической для компилятора прагмы (в MSVC это будет a #pragma warning(push) #pragma warning(disable: 4018) /* ... function */ #pragma warning(pop)) вместо использования ненужного приведения. (Броски скрывают допустимые ошибки, хорошо?;))
Билли Онил
Не правда. Cast! = Конверсия. Предупреждение - это предупреждение о неявном преобразовании, разрешенном стандартом, которое может быть небезопасным. Приведение, однако, предлагает явные преобразования в сторону, которые могут быть более опасными, чем то, о чем первоначально говорилось в предупреждении. Строго говоря, по крайней мере на x86, никакого преобразования вообще не произойдет - процессору безразлично, обрабатываете ли вы определенную часть памяти как подписанную или неподписанную, если вы используете правильные инструкции для работы с ней.
Билли Онил
Когда контейнер-агностик вызывает сложность O (N ^ 2) (и в вашем примере это так, поскольку distance () равно O (N) для list <>), я не уверен, что это преимущество :-(.
No-Bugs Hare
@ No-BugsHare В том-то и дело: мы не можем быть уверены. Если в OP мало элементов, то это, вероятно, здорово. Если у него их миллионы, то, наверное, не так много. В конце концов, это может сказать только профилирование, но хорошая новость в том, что вы всегда можете оптимизировать поддерживаемый код!
ereOn
9

Если вы не можете / не будете использовать итераторы , и если вы не можете / не использовать 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 () напрямую. Ни одна из версий не выдаст предупреждение компилятора, и это лишь небольшая модификация идиоматического цикла.

Если вы хотите отловить ошибки предположений и в выпускной версии, вы можете заменить утверждение на оператор if, который выдает что-то вроде std::out_of_range("container size exceeds range of int").

Обратите внимание, что это решает как сравнение со знаком / без знака, так и потенциальную проблему sizeof(int)! = sizeof(Container::size_type). Вы можете оставить все предупреждения включенными и использовать их для поиска реальных ошибок в других частях вашего кода.

Адриан Маккарти
источник
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());
}

источник
3

Я также могу предложить следующее решение для C ++ 11.

for (auto p = 0U; p < sys.size(); p++) {

}

(C ++ недостаточно умен для auto p = 0, поэтому я должен поставить p = 0U ....)

Степан Яковенко
источник
1
+1 для C ++ 11. Если нет веской причины, по которой вы не можете использовать C ++ 11, я думаю, что лучше использовать новые функции ... они должны быть большим подспорьем. И обязательно используйте, for (auto thing : vector_of_things)если вам действительно не нужен index.
parker.sikand
Но это решает только проблему подписи. Это не помогает, если size()возвращается тип, больший, чем unsigned int, что очень часто.
Адриан МакКарти,
3

Я дам вам лучшую идею

for(decltype(things.size()) i = 0; i < things.size(); i++){
                   //...
}

decltype является

Проверяет объявленный тип объекта или тип и категорию значения выражения.

Итак, он выводит тип things.size()и iбудет таким же видом, как и things.size(). Итак, i < things.size()будет выполнено без предупреждения

Дэниел Ким
источник
0

У меня была аналогичная проблема. Использование size_t не помогло. Я попробовал другой, который у меня работал. (как показано ниже)

for(int i = things.size()-1;i>=0;i--)
{
 //...
}
Karthik_elan
источник
0

Я бы просто сделал

int pnSize = primeNumber.size();
for (int i = 0; i < pnSize; i++)
    cout << primeNumber[i] << ' ';
Дон Гортань
источник