Як я маю справу з попередженнями про невідповідність між підписами та непідписами (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.
Billy ONeal

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, це зовсім не важливо для примітивів, де повернене значення не використовується. Однак для користувальницьких типів (де оператор перевантажений) приріст повідомлення майже завжди є менш ефективним (оскільки він повинен зробити копію об'єкта до того, як його було збільшено). Компілятори не можуть (обов'язково) оптимізувати для власних типів. Отже, єдина перевага - це послідовність (примітивів проти власних типів).
Kat

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);

Що чітко вказуватиме ваш намір компілятору: він більше не буде видавати вам попереджень.


1
Мені завжди потрібна дистанція. Можливо, це static_cast<int>(things.size())можуть бути рішення, якщо немає інших.
Andrew T,

@Andrew: Якщо ви все-таки вирішите придушити попередження, найкращим способом буде, мабуть, використання конкретної компіляторної прагми (на MSVC це буде a #pragma warning(push) #pragma warning(disable: 4018) /* ... function */ #pragma warning(pop)), а не використання непотрібного складу. (У ролях приховуються законні помилки, добре?;))
Біллі ОНіл,

Неправда. Актори! = Конверсія. Попередження є попередженням про неявне перетворення, дозволене стандартом, яке може бути небезпечним. Акторський склад, проте, запрошує учасників явних переговорів, які можуть бути більш небезпечними, ніж те, про що спочатку говорилося в попередженні. Власне кажучи, принаймні на x86, ніякого перетворення взагалі не відбудеться - процесору байдуже, чи обробляєте ви певну частину пам'яті як підписану чи непідписану, якщо ви використовуєте правильні інструкції для роботи з нею.
Billy ONeal

Коли агностик контейнера викликає складність O (N ^ 2) (і у вашому прикладі це робить, оскільки distance () - це O (N) для списку <>), я не впевнений, що це перевага :-(.
Заєць No-Bugs

@ No-BugsHare Саме в цьому полягає суть: ми не можемо бути впевнені. Якщо в OP мало елементів, то це, мабуть, чудово. Якщо у нього мільйони таких, мабуть, не так вже й багато. Зрештою, лише профілювання може сказати, але хороша новина: ви завжди можете оптимізувати підтримуваний код!
там

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 ++ недостатньо розумний для автоматичного p = 0, тому я повинен поставити p = 0U ....)


1
+1 для C ++ 11. Якщо немає поважної причини, через яку ви не можете користуватися C ++ 11, я вважаю, що найкраще використовувати нові функції ... вони мають допомогти. І обов’язково використовуйте, for (auto thing : vector_of_things)якщо вам насправді не потрібен індекс.
parker.sikand

Але це вирішує лише проблему підпису. Це не допомагає, якщо size()повертає тип, більший за непідписаний 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--)
{
 //...
}

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.