Czy Delphi przypisuje zmienną przed skonstruowaniem obiektu?

Czy Delphi przypisuje zmienną instancji, zanim obiekt zostanie w pełni skonstruowany?

Innymi słowy, podając zmienną:

var
   customer: TCustomer = nil; 

następnie konstruujemy klienta i przypisujemy go do zmiennej:

customer := TCustomer.Create;

Czy to możliwecustomer nie może byćnil, ale nie wskazuje na w pełni skonstruowanyTCustomer?

Staje się to problemem przy wykonywaniu leniwej inicjalizacji:

function SacrifialCustomer: TCustomer;
begin
   if (customer = nil) then
   begin
      criticalSection.Enter;
      try
         customer := TCustomer.Create;
      finally 
         criticalSection.Leave;
      end;
   end;
   Result := customer;
end;

Błąd jest w linii:

if (customer = nil) 

Możliwe, że inny wątek wywołuje:

customer := TCustomer.Create;

a zmiennej przypisuje się wartość przed rozpoczęciem budowy. Powoduje to wątekzałożyć żecustomer jest poprawnym obiektem po prostu dlatego, że zmienna jest przypisana.

Czy ten wielowątkowy błąd singletonowy może wystąpić w Delphi (5)?

Pytanie bonusowe

Czy istnieje akceptowany, bezpieczny wątekjednorazowa inicjalizacja wzór dla Delphi? Wiele osób wdrożyłosingletony w Delphi przez nadpisywanieNewInstance iFreeInstance; ich implementacje zawiodą w wielu wątkach.

Ściśle mówiąc nie jestem po odpowiedzi na temat sposobu wdrożenia isingel, aleleniwa inicjalizacja. Podczassingletony mogąposługiwać się leniwa inicjalizacja,leniwa inicjalizacja nie ogranicza się do singletonów.

Aktualizacja

Dwie osoby zasugerowały odpowiedźktóry zawiera powszechny błąd. Thezłamany podwójnie sprawdzony algorytm blokowania przetłumaczone na Delphi:

// Broken multithreaded version
// "Double-Checked Locking" idiom
if (customer = nil) then
begin
   criticalSection.Enter;
   try
      if (customer = nil) then
         customer := TCustomer.Create;
   finally
      criticalSection.Leave;
   end;
end;
Result := customer;

ZWikipedia:

Intuicyjnie ten algorytm wydaje się skutecznym rozwiązaniem problemu. Jednak ta technika ma wiele subtelnych problemów i zwykle należy jej unikać.

Kolejna sugestia dotycząca błędów:

function SacrificialCustomer: TCustomer;
var
  tempCustomer: TCustomer;
begin
   tempCustomer = customer;
   if (tempCustomer = nil) then
   begin
      criticalSection.Enter;
      try
         if (customer = nil) then
         begin
            tempCustomer := TCustomer.Create;
            customer := tempCustomer;
         end;
      finally
         criticalSection.Leave;
      end;
   end;
   Result := customer;
end;

Aktualizacja

utworzyłem kod i przejrzałem okno procesora. Wydaje się, że ten kompilator, z moimi ustawieniami optymalizacji, w tej wersji systemu Windows, z tym obiektem, najpierw konstruuje obiekt,następnie przypisuje zmienną:

customer := TCustomer.Create;
       mov dl,$01
       mov eax,[$0059d704]
       call TCustomer.Create
       mov [customer],eax;
Result := customer;
       mov eax,[customer];

Oczywiście nie mogę powiedzieć, że to gwarantuje, że zawsze działa w ten sposób.

questionAnswers(4)

yourAnswerToTheQuestion