Должен ли я использовать std::size_t или int в моих циклах for?

Мне просто интересно, следует ли использовать std::size_t для циклов и т.д. вместо int? Например:

#include <cstdint>

int main()
{
    for (std::size_t i = 0; i < 10; ++i) {
        // std::size_t OK here? Or should I use, say, unsigned int instead?
    }
}

В общем, что лучше всего использовать, когда использовать std::size_t?

Ответ 1

Хорошее эмпирическое правило - это все, что вам нужно сравнить в условиях цикла с чем-то, что само по себе является std::size_t.

std::size_t - это тип любого выражения sizeof и, как гарантируется, может выражать максимальный размер любого объекта (включая любой массив) в С++. Кроме того, он также может быть достаточно большим для любого индекса массива, поэтому он является естественным типом для цикла по индексу над массивом.

Если вы просто подсчитываете число, то более естественно использовать либо тип переменной, которая содержит это число, либо int или unsigned int (если достаточно большой), поскольку они должны быть естественными размер для машины.

Ответ 2

size_t - это тип результата оператора sizeof.

Используйте size_t для переменных, которые моделируют размер или индекс в массиве. size_t передает семантику: вы сразу же знаете, что она представляет собой размер в байтах или индекс, а не просто другое целое число.

Кроме того, использование size_t для представления размера в байтах помогает сделать переносимый код.

Ответ 3

Тип size_t предназначен для указания размера чего-либо, поэтому естественным его можно использовать, например, для получения длины строки и последующей обработки каждого символа:

for (size_t i = 0, max = strlen (str); i < max; i++)
    doSomethingWith (str[i]);

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

Но следите за такими вещами, как:

for (size_t i = strlen (str) - 1; i >= 0; i--)

что приведет к бесконечному циклу из-за поведения обертывания беззнаковых значений (хотя я видел, что компиляторы предупреждают об этом). Это также может быть облегчено (немного сложнее понять, но по крайней мере невосприимчива к проблемам с упаковкой):

for (size_t i = strlen (str); i-- > 0; )

Перемещая декремент в побочный эффект после проверки условия продолжения, это проверяет продолжение на значение до декремента, но все еще использует уменьшенное значение внутри цикла (поэтому цикл работает от len .. 1, а не len-1 .. 0).

Ответ 4

По определению size_t является результатом оператора sizeof. size_t был создан для обозначения размеров.

Сколько раз вы делаете что-то (10, в вашем примере) не о размерах, так зачем использовать size_t? int или unsigned int, должно быть хорошо.

Конечно, также важно, что вы делаете с i внутри цикла. Если вы передадите его функции, которая принимает unsigned int, например, выберите unsigned int.

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

Ответ 5

size_t - очень читаемый способ указать размерность элемента - длина строки, количество байтов, которое принимает указатель, и т.д. Он также переносится на разных платформах - вы обнаружите, что 64-битные и 32-разрядные оба хорошо себя ведут с системными функциями и size_t - то, что unsigned int может не выполнять (например, когда вы должны использовать unsigned long

Ответ 6

Используйте std:: size_t для индексирования/подсчета массивов в стиле C.

Для контейнеров STL у вас будет (например) vector<int>::size_type, который должен использоваться для индексирования и подсчета векторных элементов.

На практике они, как правило, являются как неподписанными int, но не гарантируются, особенно при использовании пользовательских распределителей.

Ответ 7

Вскоре большинство компьютеров будут 64-разрядными архитектурами с 64-разрядной ОС: запускать программы, работающие на контейнерах миллиардов элементов. Затем должен использовать size_t вместо int как индекс цикла, иначе ваш индекс будет обернуть вокруг в 2 ^ 32-м элементе, как на 32, так и на 64-битные системы.

Подготовьтесь к будущему!

Ответ 8

краткий ответ:

почти никогда

длинный ответ:

Всякий раз, когда вам нужно иметь вектор char больше, чем 2gb в 32-битной системе. В любом другом случае использование подписанного типа намного безопаснее, чем использование неподписанного типа.

Пример:

std::vector<A> data;
[...]
// calculate the index that should be used;
size_t i = calc_index(param1, param2);
// doing calculations close to the underflow of an integer is already dangerous

// do some bounds checking
if( i - 1 < 0 ) {
    // always false, because 0-1 on unsigned creates an underflow
    return LEFT_BORDER;
} else if( i >= data.size() - 1 ) {
    // if i already had an underflow, this becomes true
    return RIGHT_BORDER;
}

// now you have a bug that is very hard to track, because you never 
// get an exception or anything anymore, to detect that you actually 
// return the false border case.

return calc_something(data[i-1], data[i], data[i+1]);

Подписанным эквивалентом size_t является ptrdiff_t, а не int. Но использование int в большинстве случаев все же намного лучше, чем size_t. ptrdiff_t - это long в 32- и 64-разрядных системах.

Это означает, что вы всегда должны конвертировать в size_t и из него всякий раз, когда вы взаимодействуете с std::Containers, что не очень красиво. Но на нативной конференции авторы c++ отметили, что проектирование std::vector с беззнаковым size_t было ошибкой.

Если ваш компилятор выдает предупреждения о неявных преобразованиях из ptrdiff_t в size_t, вы можете сделать это явным с помощью синтаксиса конструктора:

calc_something(data[size_t(i-1)], data[size_t(i)], data[size_t(i+1)]);

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

for(const auto& d : data) {
    [...]
}

здесь несколько слов от Бьярна Страуструпа (автора c++), когда он становится нативным

Для некоторых людей эта ошибка дизайна со знаком/без знака в STL является достаточной причиной, чтобы не использовать std::vector, а вместо этого собственную реализацию.

Ответ 9

При использовании size_t будьте осторожны со следующим выражением

size_t i = containner.find("mytoken");
size_t x = 99;
if (i-x>-1 && i+x < containner.size()) {
    cout << containner[i-x] << " " << containner[i+x] << endl;
}

Вы получите false в выражении if независимо от того, какое значение у вас есть для x. Мне потребовалось несколько дней, чтобы понять это (код настолько прост, что я не делал unit test), хотя для выяснения источника проблемы требуется всего несколько минут. Не уверен, что лучше сделать бросок или использовать ноль.

if ((int)(i-x) > -1 or (i-x) >= 0)

Оба пути должны работать. Вот мой тестовый прогон

size_t i = 5;
cerr << "i-7=" << i-7 << " (int)(i-7)=" << (int)(i-7) << endl;

Выход: i-7 = 18446744073709551614 (int) (i-7) = - 2

Я бы хотел получить другие комментарии.

Ответ 10

size_t возвращается различными библиотеками, чтобы указать, что размер этого контейнера не равен нулю. Вы используете его, когда получаете один раз: 0

Однако в приведенном выше примере цикл на size_t является потенциальной ошибкой. Рассмотрим следующее:

for (size_t i = thing.size(); i >= 0; --i) {
  // this will never terminate because size_t is a typedef for
  // unsigned int which can not be negative by definition
  // therefore i will always be >= 0
  printf("the never ending story. la la la la");
}

использование целых чисел без знака имеет потенциал для создания этих типов тонких проблем. Поэтому imho я предпочитаю использовать size_t только тогда, когда я взаимодействую с контейнерами/типами, которые этого требуют.

Ответ 11

size_t - это неподписанный тип, который может содержать максимальное целочисленное значение для вашей архитектуры, поэтому он защищен от целых переполнений из-за знака (подписанный int 0x7FFFFFFF с приращением на 1 даст вам -1) или короткий размер (без знака short int 0xFFFF с шагом 1 даст вам 0).

Он в основном используется в арифметике индексирования массива/петли/адреса и так далее. Такие функции, как memset() и аналогичные, принимают только size_t, потому что теоретически у вас может быть блок памяти размером 2^32-1 (на 32-битной платформе).

Для таких простых циклов не беспокоиться и использовать только int.

Ответ 12

size_t является неподписанным int. Он скрывал детали платформы.

Ответ 13

size_t - это неподписанный тип интеграла, который может представлять наибольшее целое число в вашей системе. Используйте его только в том случае, если вам нужны очень большие массивы, матрицы и т.д.

Некоторые функции возвращают size_t, и ваш компилятор предупредит вас, если вы попытаетесь выполнить сравнения.

Избегайте этого, используя соответствующий тип данных, подписанный /unsigned, или просто тип для быстрого взлома.

Ответ 14

size_t является неподписанным int. поэтому всякий раз, когда вы хотите использовать unsigned int, вы можете использовать его.

Я использую его, когда хочу указать размер массива, counter ect...

void * operator new (size_t size); is a good use of it.

Ответ 15

for (i=0; i<Array.GetCount()-1, i++)
{

}

GetCount() возвращает size_t

Этот правильно выглядящий код неверно работает, если Array пуст.

size_t максимизирует количество, которое может быть возвращено, но это серьезная опасность. Я жалуюсь на его широкое использование. Это означает, что много кода становится опасным или альтернативно уродливым:

for (i=0; i<(int)(Array.GetCount())-1, i++)
{

}