приемлемое исправление для большинства подписанных / неподписанных предупреждений?

StackOverflow https://stackoverflow.com/questions/275853

Вопрос

Я сам убежден, что в проекте, над которым я работаю, целые числа со знаком являются лучшим выбором в большинстве случаев, даже если значение, содержащееся в нем, никогда не может быть отрицательным. (Упрощенный реверс для циклов, меньше шансов для ошибок и т. Д., Особенно для целых чисел, которые в любом случае могут содержать значения только от 0 до, скажем, 20).

Большинство мест, где это идет не так, - это простая итерация std :: vector, часто в прошлом это был массив, который позже был заменен на std :: vector. Таким образом, эти циклы обычно выглядят так:

for (int i = 0; i < someVector.size(); ++i) { /* do stuff */ }

Поскольку этот шаблон используется так часто, количество спама, предупреждающего компилятор об этом сравнении между типом со знаком и без знака, имеет тенденцию скрывать более полезные предупреждения. Обратите внимание, что у нас определенно нет векторов с более чем элементами INT_MAX, и обратите внимание, что до сих пор мы использовали два способа исправить предупреждение компилятора:

for (unsigned i = 0; i < someVector.size(); ++i) { /*do stuff*/ }

Обычно это работает, но может молча прерываться, если цикл содержит какой-либо код, например 'if (i-1 > = 0) ...' и т. д.

for (int i = 0; i < static_cast<int>(someVector.size()); ++i) { /*do stuff*/ }

Это изменение не имеет побочных эффектов, но делает цикл намного менее читабельным. (И это больше печатать.)

Итак, я пришел к следующей идее:

template <typename T> struct vector : public std::vector<T>
{
    typedef std::vector<T> base;

    int size() const     { return base::size(); }
    int max_size() const { return base::max_size(); }
    int capacity() const { return base::capacity(); }

    vector()                  : base() {}
    vector(int n)             : base(n) {}
    vector(int n, const T& t) : base(n, t) {}
    vector(const base& other) : base(other) {}
};

template <typename Key, typename Data> struct map : public std::map<Key, Data>
{
    typedef std::map<Key, Data> base;
    typedef typename base::key_compare key_compare;

    int size() const     { return base::size(); }
    int max_size() const { return base::max_size(); }

    int erase(const Key& k) { return base::erase(k); }
    int count(const Key& k) { return base::count(k); }

    map() : base() {}
    map(const key_compare& comp) : base(comp) {}
    template <class InputIterator> map(InputIterator f, InputIterator l) : base(f, l) {}
    template <class InputIterator> map(InputIterator f, InputIterator l, const key_compare& comp) : base(f, l, comp) {}
    map(const base& other) : base(other) {}
};

// TODO: similar code for other container types

То, что вы видите, - это в основном классы STL с методами, которые возвращают size_type, переопределенный, чтобы возвращать просто 'int'. Конструкторы необходимы, потому что они не наследуются.

Что бы вы подумали об этом как о разработчике, если бы увидели подобное решение в существующей базе кода?

Не могли бы вы подумать: «да, они переопределяют STL, какой огромный WTF!», или вы думаете, что это хорошее простое решение для предотвращения ошибок и повышения читабельности. Или, может быть, вы бы предпочли, чтобы мы потратили (половину) дня или около того на изменение всех этих циклов для использования std :: vector & Lt; & Gt; :: iterator?

(В частности, если это решение было объединено с запретом использования типов без знака для чего-либо, кроме необработанных данных (например, без знака) и битовых масок.)

Это было полезно?

Решение

Я сделал это сообщество вики ... Пожалуйста, отредактируйте его. Я не согласен с советом против & Quot; int & Quot; больше. Теперь я вижу, что это неплохо.

Да, я согласен с Ричардом. Вы никогда не должны использовать 'int' в качестве переменной подсчета в цикле, как те. Ниже приведено описание того, как вы можете создавать различные циклы с использованием индексов (хотя причин для этого мало, иногда это может быть полезно).

Вперед

for(std::vector<int>::size_type i = 0; i < someVector.size(); i++) {
    /* ... */
}

Назад

Вы можете сделать это, это совершенно определенное поведение:

for(std::vector<int>::size_type i = someVector.size() - 1; 
    i != (std::vector<int>::size_type) -1; i--) {
    /* ... */
}

Вскоре, с появлением c ++ 1x (следующей версии C ++), вы можете сделать это следующим образом:

for(auto i = someVector.size() - 1; i != (decltype(i)) -1; i--) {
    /* ... */
}

Уменьшение значения ниже 0 приведет к тому, что я обернусь вокруг, потому что он без знака.

Но неподписанный вызовет ошибки в

Это никогда не должно быть аргументом, чтобы сделать это неправильно (используя 23.1 p5 Container Requirements).

Почему бы не использовать std :: size_t выше?

Стандарт C ++ определяет в T::size_type, что T, поскольку Container является некоторым std::size_t, что этот тип является некоторой реализацией, определенной для целочисленного типа без знака. Теперь, используя i для (std::size_t)-1 выше, ошибки будут скрытыми. Если someVector.size() == 0 меньше или больше, чем <=>, то оно переполнится <=> или даже не достигнет <=>, если <=>. Аналогично, условие цикла было бы полностью нарушено.

Другие советы

Не выводите публично из контейнеров STL. У них есть не виртуальные деструкторы, которые вызывают неопределенное поведение, если кто-либо удаляет один из ваших объектов через указатель на базу. Если вы должны получить, например, из вектора, сделайте это в частном порядке и покажите части, которые вы хотите показать, с помощью using объявлений.

Здесь я бы просто использовал size_t в качестве переменной цикла. Это просто и читабельно. Постер, прокомментировавший, что использование индекса int представляет вас как n00b, верен. Тем не менее, использование итератора для цикла по вектору представляет вас немного более опытным n00b - тем, кто не понимает, что оператор индекса для вектора имеет постоянное время. (vector<T>::size_type является точным, но ненужным многословным ИМО).

Пока я не думаю, что & используйте итераторы, иначе вы посмотрите n00b " является хорошим решением проблемы, производная от std :: vector выглядит гораздо хуже.

Во-первых, разработчики ожидают, что vector будет std: .vector, а map будет std :: map. Во-вторых, ваше решение не масштабируется для других контейнеров или для других классов / библиотек, которые взаимодействуют с контейнерами.

Да, итераторы безобразны, циклы итераторов плохо читаются, а typedefs только скрывают беспорядок. Но, по крайней мере, они масштабируются и являются каноническим решением.

Мое решение? макрос для каждого. Это не без проблем (в основном это макрос, чёрт возьми), но оно понимает смысл. Это не так продвинуто, как, например, этот , но выполняет свою работу.

Обязательно используйте итератор. Вскоре вы сможете использовать тип auto для лучшей читабельности (одной из ваших проблем), например:

for (auto i = someVector.begin();
     i != someVector.end();
     ++i)

Пропустить индекс

Самый простой подход - обойти проблему, используя итераторы, циклы на основе диапазона или алгоритмы:

for (auto it = begin(v); it != end(v); ++it) { ... }
for (const auto &x : v) { ... }
std::for_each(v.begin(), v.end(), ...);

Это хорошее решение, если вам на самом деле не нужно значение индекса. Он также легко обрабатывает обратные циклы.

Используйте подходящий тип без знака

Другой подход заключается в использовании типа размера контейнера.

for (std::vector<T>::size_type i = 0; i < v.size(); ++i) { ... }

Вы также можете использовать std::size_t (из < cstddef >). Есть те, кто (правильно) указывает, что std::vector<T>::size_type может не совпадать с типом size_type (хотя обычно это так). Однако вы можете быть уверены, что контейнер int поместится в size_as_int. Так что все в порядке, если вы не используете определенные стили для обратных циклов. Мой предпочтительный стиль для обратного цикла такой:

for (std::size_t i = v.size(); i-- > 0; ) { ... }

С этим стилем вы можете безопасно использовать <=>, даже если он больше, чем <=>. Стиль обратных циклов, показанный в некоторых других ответах, требует приведения -1 к точно правильному типу и, следовательно, не может использовать более простой для ввода <=>.

Используйте подписанный тип (осторожно!)

Если вы действительно хотите использовать подписанный тип (или если ваш практически требует одного ), например <=>, тогда вы можете использовать этот крошечный шаблон функции, который проверяет базовое предположение в отладочных сборках и делает преобразование явным, чтобы вы не получили предупреждение компилятора сообщение:

#include <cassert>
#include <cstddef>
#include <limits>

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(v); ++i) { ... }

Или обратный цикл традиционным способом:

for (int i = size_as_int(v) - 1; i >= 0; --i) { ... }

Уловка <=> лишь немного более типична, чем циклы с неявными преобразованиями, вы проверяете базовое предположение во время выполнения, вы выключаете предупреждение компилятора с явным приведением, вы получаете ту же скорость, что и не отладочные сборки потому что он почти наверняка будет встроенным, а оптимизированный объектный код не должен быть больше, потому что шаблон не делает ничего, что компилятор уже не делал неявным образом.

Вы переосмысливаете проблему.

Использование переменной size_t предпочтительнее, но если вы не доверяете своим программистам правильно использовать unsigned, используйте приведение и просто разберитесь с уродством. Попросите стажера изменить их все и не беспокойтесь об этом после этого. Включите предупреждения как ошибки, и новые не появятся. Ваши циклы могут быть & Quot; ugly & Quot; сейчас, но вы можете понять это как последствия вашей религиозной позиции в отношении подписанных и неподписанных.

vector.size() возвращает size_t переменную, поэтому просто измените int на <=>, и все будет в порядке.

Ответ Ричарда является более правильным, за исключением того, что для простого цикла много работы.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top