Записываемый локальный const

Не стреляйте в меня, но это первый раз, когда я увидел использование локального записываемого const (или, может быть, я просто слишком сенил...): "Путь WinAPI (от Peter Below от TeamB)"

Взгляните на локальный const FullScreen: Boolean = False;, а затем FullScreen := not FullScreen;

Сначала я решил, что это была новая функция с современными версиями Delphi, но она также работает с моим D5. Поэтому мой вопрос: являются ли локальные константы, доступные для записи, точно такими же, что и объявление глобальной константы для записи?

например.

procedure TForm1.Button1Click(Sender: TObject);
Const
  LocalConst: Boolean = False;
begin
  LocalConst := not LocalConst;
  if LocalConst then Beep;
end;

Работает так же, как этот код?

Const
  GlobalConst_Button2Click: Boolean = False;

procedure TForm1.Button2Click(Sender: TObject);
begin
  GlobalConst_Button2Click := not GlobalConst_Button2Click;
  if GlobalConst_Button2Click then Beep;
end;

Или, LocalConst является локальным для него способом, то есть статическим? Является ли эта константа безопасной? Может ли кто-нибудь пролить свет на эту проблему?

Ответ 1

Код с локальной и глобальной типизированной константой делает то же самое.

Как уже указывал Дэвид, глобальная статическая переменная (aka typed constant) доступна во всей программе, а локальная статическая переменная - нет. Ниже я буду ссылаться на typed constants как static variables, потому что это действительно так.

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

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

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

Короче
Использование статических vars в потоковом коде - очень плохая идея, если вы точно не знаете, что делаете.

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

Если вы хотите делиться данными между потоками, лучше использовать threadvar,
см. http://en.wikipedia.org/wiki/Thread-local_storage
и: https://stackoverflow.com/search?q=delphi+threadvar

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

Ответ 2

Являются ли локальные константы, доступные для записи, точно такими же, как объявление глобальной константы записи?

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

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

Ответ 3

Я немного опаздываю на вечеринку, но мне все же хочется добавить информацию о записываемых константах.

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

Для тех, кто заинтересован в использовании перезаписываемых констант: Я счел полезным подражать функциональности "Promise", чтобы сделать функцию "Lazy". Конечно, delphi не поддерживает Promises, поэтому это только частично эффективно.

Рассмотрим функцию для подсчета количества слов в строке:

function CountWords(Input: String):Integer;
var
    Worker: TStringList;
begin
  Worker := TStringList.Create;
  Worker.DelimitedText := Input;
  Result := Worker.Count;
  Worker.Free;
end;

Теперь представьте, что это много раз в нашей программе. Объект TStringList будет создан и освобожден каждый раз, когда мы это делаем, тем самым делая дополнительную работу. Вы могли бы решить это, создав глобальную переменную Worker_CountWords, инициализируя ее при запуске программы и используя ее в своей функции, но посмотрите на это:

function CountWords(Input: String):Integer;
{$J+} //Enable writable constants
const
    Worker: TStringList = nil;
{$J-} //Disable writable constants
begin
  if Worker = nil then
  begin
    Worker := TStringList.Create;
    //Other Initialization code here
  end;
  Worker.DelimitedText := Input;
  Result := Worker.Count;
end;

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

Также не требуется иметь {$ WRITABLECONST ON} для изменения постоянных значений, вы также можете сделать что-то вроде этого:

procedure DoSomeWork;
const
  FirstCall : TDateTime = 0;
begin
  if FirstCall = 0 then
    PDateTime(@FirstCall)^ := Now;
  Writeln(TimeToStr(FirstCall));
  //some actual work here
end;

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

P.S. Будьте осторожны с параметрами функции const, так как вы можете передавать фактические константы, такие как foo(12), и пытаться модифицировать, что, вероятно, может что-то испортить...

Ответ 4

Я не понял этот вопрос полностью, поэтому я отвечу на все случаи, которые могут произойти:

  • Если вы не понимаете, как различаются два из моих CountWords примеров: сначала создается и освобождается экземпляр класса каждый раз, когда он вызывается. Второй создает только экземпляр класса при первом вызове, затем использует его до завершения программы.
  • Если вы имеете в виду, как это будет отличаться, если вы создадите синглтон и внедрили CountWords в нем: он не будет отличаться по функциональности, но вам придется написать для него целый новый класс, а затем инициализировать его при запуске программы и использовать его позже. Кроме того, есть много критики в отношении использования синглтонов (см. Wiki для получения дополнительной информации), поэтому я бы этого не делал.
  • Если вы спрашиваете о фактической работе, выполняемой этими двумя примерными функциями: они, очевидно, дают одинаковые результаты, но первый из них не оптимизирован для работы в среде, которая многократно вызывает эту функцию. Как я уже говорил, вы можете достичь одинаковых результатов, объявив глобальную переменную, но если вы это сделаете - вы увидите свою глобальную переменную всюду в своей программе, тогда как она нужна только в одном конкретном месте и нигде больше.

P.S. Я действительно ошибался в параметрах функции const, оказывается, что эти вещи ведут себя по-разному в разных версиях delphi. Константные параметры действовали точно так же, как параметры var в Delphi 6, но в Delphi XE2 кажется, что локальная переменная создается в любом случае. В любом случае я не рекомендую возиться с параметрами функции const.