Когда использовать std :: size_t?


201

Мне просто интересно, я должен использовать 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?

Ответы:


186

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

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

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


41
Стоит отметить , что не используя , size_tкогда вы должны можете привести к ошибкам безопасности .
BlueRaja - Дэнни Пфлюгофт

5
Мало того, что int "естественен", но и смешивание типов со знаком и без знака может также привести к ошибкам безопасности. Беззнаковые индексы - трудная задача и хорошая причина использовать собственный векторный класс.
Джо Со

2
@JoSo Существует также ssize_tдля подписанных значений.
EntangledLoops

70

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

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

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


32

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


14
Кстати, это плохая практика - вызывать strlenкаждую итерацию цикла. :) Вы можете сделать что-то вроде этого:for (size_t i = 0, len = strlen(str); i < len; i++) ...
musiphil

1
Даже если это тип со знаком, вы должны следить за граничными условиями, возможно, даже в большей степени, поскольку переполнение со знаком является неопределенным поведением.
Адриан Маккарти

2
Обратный отсчет можно сделать следующим (позорным) способом:for (size_t i = strlen (str); i --> 0;)
Джо Так

1
@JoSo, это на самом деле довольно хитрый трюк, хотя я не уверен, что мне нравится введение -->оператора «идет» (см. Stackoverflow.com/questions/1642028/… ). Включили ваше предложение в ответ.
paxdiablo

Можете ли вы сделать простой if (i == 0) break;в конце цикла for (например, for (size_t i = strlen(str) - 1; ; --i)(хотя мне нравится ваш, хотя я просто задаюсь вопросом, будет ли это работать так же хорошо).
RastaJedi

13

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

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

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

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


10

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


9

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

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

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

Всякий раз, когда вам нужно иметь вектор 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, а вместо этого собственную реализацию.


1
Я понимаю, откуда они берутся, но я все еще думаю, что это странно писать for(int i = 0; i < get_size_of_stuff(); i++). Теперь, конечно, вы можете не захотеть делать много сырых циклов, но - давай, ты тоже их используешь.
einpoklum

Единственная причина, по которой я использую сырые циклы, заключается в том, что библиотека алгоритма c ++ разработана довольно плохо. Есть языки, такие как Scala, которые имеют гораздо лучшую и более развитую библиотеку для работы с коллекциями. Тогда сценарий использования сырых циклов практически исключен. Существуют также подходы к улучшению c ++ с помощью нового и лучшего STL, но я сомневаюсь, что это произойдет в течение следующего десятилетия.
Арне

1
Я получаю это без знака я = 0; подтвердить (i-1, MAX_INT); но я не понимаю, почему вы говорите: «если у меня уже был недостаток, это становится правдой», потому что поведение арифметики на неподписанных целых всегда определяется, т.е. результат - результат по модулю размера наибольшего представимого целого числа. Так что, если i == 0, то i-- становится MAX_INT, а затем i ++ снова становится 0.
Мабрахам

@mabraham Я посмотрел внимательно, и вы правы, мой код не лучший, чтобы показать проблему. Как правило, это x + 1 < yэквивалентно x < y - 1, но они не с unigend целыми числами. Это может легко привести к ошибкам, когда вещи преобразуются, которые предполагаются эквивалентными.
Арне

8

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

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

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


2
С gcc в linux std::size_tобычно unsigned long(8 байт в 64-битных системах), а не unisgned int(4 байта).
Рафак

5
Массивы в стиле C не индексируются size_t, поскольку индексы могут быть отрицательными. Можно использовать size_tдля собственного экземпляра такого массива, если вы не хотите идти отрицательным.
Йоханнес Шауб -

Сравнения на U64 так же быстры, как на U32? Я рассчитывал на серьезные потери производительности за использование u8s и u16s в качестве сторожевых, но я не знаю, добились ли Intel своего успеха на 64-х.
Crashworks

2
Поскольку индексирование массива в стиле C эквивалентно использованию оператора +в указателях, может показаться, что ptrdiff_tоно используется для индексов.
Павел Минаев

8
Что касается vector<T>::size_type(и то же самое для всех других контейнеров), это на самом деле довольно бесполезно, потому что он гарантированно гарантированно будет size_t- это typedef'd to Allocator::size_type, а для ограничений на это в отношении контейнеров см. 20.1.5 / 4 - в частности, size_typeдолжен быть size_tи difference_typeдолжен быть ptrdiff_t. Конечно, значение по умолчанию std::allocator<T>удовлетворяет этим требованиям. Так что просто используйте короче size_tи не мешайте остальной части партии :)
Павел Минаев

7

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

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


Ваш аргумент идет только до значения, которое нужно, long intа не int. Если size_tэто применимо к 64-битной ОС, то же самое относится и к 32-битной ОС.
einpoklum

4

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

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

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


2
обратите внимание, что (int)(i - 7)это нижестоящее значение, которое преобразуется в intпоследующее время, но int(i) - 7не является поточным, поскольку вы сначала конвертируете iв int, а затем вычитаете 7. Кроме того, я нашел ваш пример запутанным.
hochl

Я хочу сказать, что int обычно безопаснее, когда вы делаете вычитания.
Кемин Чжоу

4

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 только тогда, когда я взаимодействую с контейнерами / типами, которые требуют этого.


Кажется, Everone использует size_t в цикле, не беспокоясь об этой ошибке, и я научился этому нелегко
Pranjal Gupta

-2

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

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

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


-3

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

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

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


4
Используйте его только если вы хотите избежать ошибок и брешей в безопасности.
Крейг МакКуин

2
На самом деле он может не представлять наибольшее целое число в вашей системе.
Адриан Маккарти

-4

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

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

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

10
На самом деле это не обязательно то же самое, что и unsigned int. Он не подписан, но может быть больше (или, я думаю, меньше, хотя я не знаю ни одной платформы, где это правда), чем int.
Тодд Гамблин

Например, на 64-разрядной машине это size_tможет быть 64-разрядное целое число без знака, а на 32-разрядной машине это только 32-разрядное целое число без знака.
HerpDerpington
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.