В C ++ size_t
(или, вернее, T::size_type
«обычно» size_t
; т. Е. unsigned
Тип) используется как возвращаемое значение для size()
аргумента и operator[]
т. Д. (См std::vector
. И т. Д.)
С другой стороны, языки .NET используют int
(и, необязательно long
) для той же цели; фактически CLS-совместимые языки не обязаны поддерживать неподписанные типы .
Учитывая, что .NET новее, чем C ++, что-то подсказывает мне, что могут быть проблемы с использованием unsigned int
даже для вещей, которые «не могут быть» отрицательными, таких как индекс массива или длина. Является ли подход C ++ "историческим артефактом" для обратной совместимости? Или между этими двумя подходами существуют реальные и существенные компромиссные решения?
Почему это важно? Хорошо ... что я должен использовать для нового многомерного класса в C ++; size_t
или int
?
struct Foo final // e.g., image, matrix, etc.
{
typedef int32_t /* or int64_t*/ dimension_type; // *OR* always "size_t" ?
typedef size_t size_type; // c.f., std::vector<>
dimension_type bar_; // maybe rows, or x
dimension_type baz_; // e.g., columns, or y
size_type size() const { ... } // STL-like interface
};
-1
возвращается из функций, которые возвращают индекс, чтобы указать «не найден» или «вне диапазона». Это также возвращается изCompare()
функций (реализацииIComparable
). 32-разрядный тип int считается типом go для общего числа, и я надеюсь, что это очевидные причины.Ответы:
Да. Для определенных типов приложений, таких как обработка изображений или обработка массивов, часто бывает необходимо получить доступ к элементам относительно текущей позиции:
В этих типах приложений вы не можете выполнить проверку диапазона с целыми числами без знака, не задумываясь тщательно:
Вместо этого вы должны изменить свое выражение проверки диапазона. Это главное отличие. Программисты также должны помнить правила целочисленного преобразования. В случае сомнений перечитайте http://en.cppreference.com/w/cpp/language/operator_arithmetic#Conversions
Многим приложениям не нужно использовать очень большие индексы массива, но им нужно выполнять проверки диапазона. Кроме того, многие программисты не обучены такому выражению перегруппировки гимнастики. Единственная упущенная возможность открывает дверь к подвигу.
C # действительно предназначен для тех приложений, которым не нужно более 2 ^ 31 элементов на массив. Например, приложению электронной таблицы не нужно иметь дело с таким количеством строк, столбцов или ячеек. C # имеет дело с верхним пределом, имея необязательную проверенную арифметику, которую можно включить для блока кода с ключевым словом, не связываясь с параметрами компилятора. По этой причине C # поддерживает использование целого числа со знаком. Когда эти решения рассматриваются в целом, это имеет смысл.
C ++ просто другой, и сложнее получить правильный код.
Что касается практической важности разрешения арифметики со знаком для устранения потенциального нарушения «принципа наименьшего удивления», то примером является OpenCV, который использует 32-разрядное целое число со знаком для индекса элемента матрицы, размера массива, числа каналов в пикселях и т. Д. обработка является примером области программирования, которая интенсивно использует относительный индекс массива. Неполное целое число без знака (отрицательный результат обернут) сильно усложнит реализацию алгоритма.
источник
Этот ответ действительно зависит от того, кто будет использовать ваш код и какие стандарты они хотят видеть.
size_t
целочисленный размер с целью:Таким образом, всякий раз, когда вы хотите работать с размером объектов в байтах, вы должны использовать
size_t
. Сейчас во многих случаях вы не используете эти измерения / индексы для подсчета байтов, но большинство разработчиков предпочитают использоватьsize_t
их для согласованности.Обратите внимание, что вы всегда должны использовать,
size_t
если ваш класс должен выглядеть и чувствовать себя как класс STL. Все классы STL в спецификации используютsize_t
. Это допустимо для компилятора typedefsize_t
бытьunsigned int
, и это также допустимо для его определения типаunsigned long
. Если вы используетеint
илиlong
напрямую, вы в конечном итоге столкнетесь с компиляторами, где человек, который думает, что ваш класс следует стилю STL, попадает в ловушку, потому что вы не следовали стандарту.Что касается использования подписанных типов, есть несколько преимуществ:
int
, но гораздо сложнее загромождать кодunsigned int
.int32_t
иuint32_t
). Это может упростить взаимодействие APIБольшой недостаток подписанных типов очевиден: вы теряете половину своего домена. Число с подписью не может считаться таким же высоким, как число без знака. Когда появился C / C ++, это было очень важно. Нужно было уметь использовать все возможности процессора, и для этого нужно было использовать беззнаковые числа.
Для целевых приложений .NET не было такой сильной необходимости в полнодоменном индексе без знака. Многие из целей для таких чисел просто недопустимы в управляемом языке (на ум приходит пул памяти). Кроме того, как только вышел .NET, 64-битные компьютеры были явно в будущем. Мы далеки от того, чтобы нуждаться в полном диапазоне 64-битного целого числа, поэтому жертвовать одним битом не так болезненно, как это было раньше. Если вам действительно нужно 4 миллиарда индексов, вы просто переключаетесь на использование 64-битных целых чисел. В худшем случае вы запускаете его на 32-битной машине, и это немного медленно.
Я рассматриваю торговлю как одно из удобных. Если у вас достаточно вычислительной мощности, и вы не возражаете потратить немного своего типа индекса, который вы никогда не будете использовать никогда, тогда удобно просто набирать
int
илиlong
уходить от него. Если вы обнаружите, что действительно хотели этот последний бит, то вам, вероятно, следовало бы обратить внимание на подписанность ваших чисел.источник
size()
былаreturn bar_ * baz_;
; разве это не создает потенциальную проблему с целочисленным переполнением (обходом), которого у меня не было бы, если бы я не использовалsize_t
?bar_ * baz_
можно переполнить целое число со знаком, но не целое число без знака. Ограничивая себя C ++, стоит отметить, что переполнение без знака определено в спецификации, но переполнение со знаком является неопределенным поведением, поэтому, если желательна арифметика по модулю целых чисел без знака, определенно используйте их, потому что они действительно определены!size()
захлестнула подписанное умножение, вы на язык UB земли. (и вfwrapv
режиме, см. далее :) Когда затем , чуть-чуть больше, он переполнит беззнаковое умножение, вы окажетесь на земле пользовательского кода-ошибки - вы получите поддельный размер. Поэтому я не думаю, что неподписанные покупает здесь много.Я думаю, что ответ Руонга выше уже превосходно выдвигает на первый план проблемы.
Я добавлю свой 002:
size_t
то есть размер, который ...... требуется только для индексов диапазона
sizeof(type)==1
, если вы имеете дело сchar
типами byte ( ). (Но, заметим, он может быть меньше, чем тип ptr :xxx::size_type
может использоваться в 99,9% случаев, даже если это тип со знаком размера. (сравнитьssize_t
)std::vector
и друзья решилиsize_t
, в неподписанных тип, для размера и индексации , по мнению некоторых , чтобы быть недостатком конструкции. Я согласен. (Серьезно, потратьте 5 минут и посмотрите молниеносный доклад CppCon 2016: Джон Калб «unsigned: Руководство по улучшению кода» .)size_t
чтобы соответствовать стандартной библиотеке, или используйте ( подписанный )intptr_t
илиssize_t
для простых и менее подверженных ошибкам расчетов индексации.intptr_t
если вы хотите подписать и хотите, чтобы размер машинного слова или использовалсяssize_t
.Чтобы прямо ответить на вопрос, это не совсем «исторический артефакт», так как теоретическая проблема необходимости решения более половины («индексации» или) адресного пространства должна , так или иначе, решаться на низкоуровневом языке, таком как C ++.
Оглядываясь назад, я, лично , думаю, что это конструктивный недостаток , что стандартная библиотека использует неподписанные
size_t
повсюду , даже если она не представляет собой необработанный объем памяти, а емкость типизированных данных, как для коллекций:Я повторю совет Джона здесь:
(* 1) то есть unsigned == bitmask, никогда не делайте математику с ней (здесь попадает первое исключение - вам может понадобиться счетчик, который переносит - это должен быть тип без знака.)
(* 2) количества, означающие что-то, на что вы рассчитываете и / или делаете математику
источник
ssize_t
, определяемый как подписанный кулонsize_t
вместоintptr_t
, который может хранить любой (не член) указатель и, следовательно, может быть больше?size_t
определение. Смотрите size_t против intptr и en.cppreference.com/w/cpp/types/size_t Узнали что-то новое сегодня. :-) Я думаю, что остальные аргументы верны, я посмотрю, смогу ли я исправить используемые типы.Я просто добавлю, что по соображениям производительности я обычно использую size_t, чтобы гарантировать, что просчеты приводят к недостаточному переполнению, что означает, что обе проверки диапазона (ниже нуля и выше размера ()) могут быть уменьшены до одного:
используя подписанный int:
используя unsigned int:
источник