Должен ли я использовать целые числа без знака для подсчета членов? - PullRequest
9 голосов
/ 28 апреля 2009

Должен ли я использовать целые числа без знака для моих членов класса подсчета?

Ответ

Например, предположим, что класс

TList <T> = class
private
  FCount : Cardinal;
public
  property Count : Cardinal read FCount;
end;

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

Теперь итерация по списку выглядит следующим образом:

for I := 0 to List.Count - 1 do
  Writeln (List [I]);

Когда количество элементов, хранящихся в списке, равно нулю, компилятор пытается оценить

List.Count - 1

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

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

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

Если это полная чушь, пожалуйста, укажите мне:)

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

Ответы [ 6 ]

15 голосов
/ 28 апреля 2009

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

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

10 голосов
/ 28 апреля 2009

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

Возьмем, например, C / C ++: size_t - это тип целого числа для размеров и распределения памяти, он не имеет знака, но ptrdiff_t - это тип смещения, которое вы получаете, когда вычитаете один указатель из другого, обязательно подписано. Хотите знать, сколько элементов вы разместили в массиве? Возможно, вы вычитаете адрес элемента first из адреса элемента last+1 и делите на sizeof(element-type)? Что ж, теперь вы просто смешали целые числа со знаком и без знака.

9 голосов
/ 28 апреля 2009

Относительно вашего заявления о том, что "Я думаю, что в целом это хороший принцип - всегда использовать наименее общий (эрг. Самый особенный) тип." типа, который вызовет у вас меньше всего беспокойства и проблем.

Вообще для меня это int с тех пор:

  1. У меня обычно нет списков с 2 31 или более элементов в них.
  2. У вас не должно быть и таких больших списков: -)
  3. Мне не нравится, когда в моем коде возникают особые крайние случаи.

Но это действительно вопрос стиля. Если «чистота» кода важнее для вас, чем краткость кода, ваш метод лучше всего (с модификациями, позволяющими уловить крайние случаи). Сам я предпочитаю краткость, так как крайние случаи имеют тенденцию загромождать код и уменьшать понимание.

8 голосов
/ 29 апреля 2009

Не.

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

Имейте в виду, что "unsigned" переводит not на "положительный", он переводится как "без знака", что отличается. Термин «неподписанный» был выбран по уважительной причине (и назвав его «Кардинал» в Delphi было плохим выбором IMO).

Типы без знака предназначены для необработанных спецификаций хранения, побитовых операций, кода ASM, встроенных контроллеров и других специальных применений. Когда вы занимаетесь программированием высокого уровня, вы должны забыть, что когда-либо слышали о неподписанных типах.

3 голосов
/ 28 апреля 2009

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

3 голосов
/ 28 апреля 2009

Мораль: используйте итераторы и foreach, когда можете, потому что это полностью исключает этот вопрос.

...