Коли використовувати 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 - Danny Pflughoeft

5
Мало того, що int є "природним", але змішування підписаного та непідписаного типу може також призвести до помилок безпеки. Непідписані індекси - це біль, яку можна впоратися, і вагомий привід використовувати користувацький векторний клас.
Jo So

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

1
@JoSo, це насправді досить акуратний трюк, хоча я не впевнений, що мені подобається введення -->оператора "йде" (див. Stackoverflow.com/questions/1642028/… ). Включіть вашу пропозицію у відповідь.
paxdiablo

Чи можете ви зробити простий if (i == 0) break;в кінці циклу for (наприклад for (size_t i = strlen(str) - 1; ; --i)
,.

13

За визначенням, size_tце результат sizeofоператора. size_tстворено для позначення розмірів.

Кількість разів, коли ви щось робите (у вашому прикладі 10), не стосується розмірів, так навіщо використовувати size_t? int, або unsigned int, має бути гаразд.

Звичайно, також актуально те, що ви робите iвсередині циклу. Якщо ви передаєте його функції, яка займає unsigned int, наприклад, вибір unsigned int.

У будь-якому випадку, я рекомендую уникати конверсій неявного типу. Зробити всі перетворення типів явними.


10

size_tце дуже читабельний спосіб визначити розмірність елемента - довжина рядка, кількість байтів, що займає вказівник, тощо. Він також портативний на різних платформах - ви побачите, що 64-бітні та 32-бітні обидва чудово поводяться з системними функціями та size_t- щось, що unsigned intможе не зробити (наприклад, коли слід використовуватиunsigned long


9

Коротка відповідь:

майже ніколи

довга відповідь:

Кожен раз, коли вам потрібно мати вектор char, більший за 2 Гб в 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 ::, що не дуже красиво. Але на щоденній конференції, яка відкрилася, автори 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) {
    [...]
}

тут кілька слів від Bjarne Stroustrup (автор C ++), коли ви рідні

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


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

Єдина причина, по якій я використовую сировину, полягає в тому, що бібліотека алгоритмів c ++ розроблена досить погано. Є такі мови, як Scala, які мають набагато кращу і розвинуту бібліотеку для роботи над колекціями. Тоді випадки використання сирих петель майже усунені. Існують також підходи до вдосконалення c ++ за допомогою нової та кращої STL, але я сумніваюся, що це станеться протягом наступного десятиліття.
Арн

1
Я отримую це без підпису i = 0; стверджувати (i-1, MAX_INT); але я не розумію, чому ви говорите "якщо у мене вже був підтік, це стає істинним", тому що поведінка арифметики на неподписаних ints завжди визначається, тобто. результат є модулем результату розміром найбільшого цілого числа, що представляється. Отже, якщо i == 0, то i-- стає MAX_INT, а потім i ++ знову стає 0.
мабрахам

@mabraham Я уважно подивився, і ти маєш рацію, мій код не найкращий, щоб показати проблему. Зазвичай це x + 1 < yеквівалентно x < y - 1, але вони не мають цілих чисел. Це може легко ввести помилки, коли речі трансформуються, які вважаються еквівалентними.
Арн

8

Використовуйте std :: size_t для індексації / підрахунку масивів у стилі С.

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

На практиці вони, як обидва, не підписані вставки, але це не гарантується, особливо при використанні спеціальних алокаторів.


2
З gcc на Linux, std::size_tяк правило, unsigned long(8 байт у 64-бітовій системі), а не unisgned int(4 байти).
rafak

5
Масиви у стилі С не індексуються через size_t, оскільки індекси можуть бути негативними. Можна використати size_tдля власного екземпляра такого масиву, якщо він не хоче негативувати.
Йоханнес Шауб - ліб

Чи порівняння на u64 є настільки ж швидкими, як порівняння на u32? Я приурочив суворі штрафні санкції за використання u8s та u16s в якості дозорних циклів, але я не знаю, чи Intel зібрав свої дії разом на 64-х.
Crashworks

2
Оскільки індексація масиву в стилі С еквівалентна використанню оператора +в покажчиках, то, здається, ptrdiff_tсаме цей використовується для індексів.
Павло Мінаєв

8
Що стосується vector<T>::size_type(і ditto для всіх інших контейнерів), це насправді досить марно, тому що це фактично гарантовано size_t- це typedef'd 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: й елемент, на обох 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;
}

Ви отримаєте помилковий вираз 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.
Kemin Zhou

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) або короткого розміру (непідписаний короткий int 0xFFFF, збільшений на 1, дасть вам 0).

В основному використовується в арифметиці масивів / циклів / адреси та ін. Функції, подібні memset()та подібні, приймаються size_tлише тому, що теоретично у вас може бути блок пам'яті розміру 2^32-1(на 32-бітній платформі).

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


-3

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

Деякі функції повертають size_t, і ваш компілятор попередить вас, якщо ви спробуєте порівняти.

Уникайте цього, використовуючи відповідний тип даних, що підписується / не підписується, або просто набирайте текст для швидкого злому.


4
Використовуйте його лише в тому випадку, якщо ви хочете уникнути помилок і захисних дірок.
Крейг МакКвін

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

-4

size_t не підписаний int. тому коли ви хочете без підпису int, ви можете використовувати його.

Я використовую його, коли хочу вказати розмір масиву, лічильник ect ...

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

10
Насправді це не обов'язково те саме, що без підписаного int. Це є беззнаковим, але це може бути більше (або менше , я думаю , хоча я не знаю ні платформ , де це вірно) , ніж в міжнар.
Тодд Гамблін

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