среда, 18 марта 2015 г.

Briefly. About constructors

Original in Russian: http://programmingmindstream.blogspot.ru/2014/09/blog-post_26.html

Depression  – has “partly” vanished.

Lots of errors have been found.

Particularly – THANKS to NameRec - http://programmingmindstream.blogspot.ru/2014/09/blog-post_23.html?showComment=1411514362019#c1699121675114974966 (in Russian)

Load tests based on GUI-testing have been written.

They emulate the work of real-life users in the distributed system.

On a number of computers.

According to the order:

1. Open a document (out of the predefined list) randomly.
2. Insert “two or three words”.
3. Save it.
4. Close the document.

By the way, the test code:

USES
 QuickTestsUtils.script
;
 
Test TK565491886
 
 ARRAY VAR "Documents list"
 [ Document name ] >>> "Documents list"         
 
 BOOLEAN VAR Eternity
 TRUE >>> Eternity
 WHILE Eternity (
          INTEGER VAR "Random document"
          ( "Documents list" array:Count Random "Documents list" [i] ) >>> " Random document" 
     Parameters: ( "Document from base {("Random document")}" )
     Execute ( 
  "Input the text {('We change the text and save it!!!')}"
  "Press Enter" // - in order to separate documents paragraphs, otherwise they be perceived as “too long” 
  "Save the document"
  "Process the messages" // - to redraw the application
   )
        )
;
 
TK565491886

My tests have been working for three days now.

About 9 Gbs of data has been processed.

I left it for a week-end. We’ll see what we’ll have.

I’ll write a profound analysis of results.

I can tell one thing now – even if function ReadFile has “read something” and returned ReadSize = SizeToRead – “that is not the reason to calm down”. Even if it “read” what it was intended to. Even if the results coincide with the file data.

One should check Result of ReadFile function, the BOOL. Finally, GetLastError.

For example, it can return LockViolation or NetworkBusy.

Trivial. Yeah.

But I’ll write about it “some time later”.

Now I’d like to write about constructors.

Based on:
Link. Getting of resource is initialization (RAII). Some of “my own ideas” (in Russian)
Briefly. Once again about factories
Briefly. Some more “reasoning about RAII”
Briefly. Again about factories
Briefly. About factories
My own implementation of IUnknown and reference counting. And mixins

Why we always need to use FreeAndNil instead of Free (in Russian)  – this one worth a particular and attentive reading, since my thought comes from “the same paradigm”. Virtuality. And descendant classes.

Briefly. For those who don’t like FreeAndNil (in Russian)
Today I’ve got a five-time evidence of the fact that we have to write FreeAndNil instead of Free (in Russian)
Rule 9: Never call virtual functions in constructor or destructor (in Russian)
Virtual functions in constructor or destructor(in Russian)
Gunsmoker wrote about destructors and I want to write about constructors.

And so, here I come.

Usually constructors are written in this way:

type
 TObject1 = class
 end;//TObject1
 
 TObject2 = class
 end;//TObject2
 
 TA = class
  private
   f_SomeObject1 : TObject1;
  public
   constructor Create;
 end;//TA
 
 TB = class(TA)
  private
   f_SomeObject2 : TObject2;
  public
   constructor Create;
 end;//TB
 
...
 
constructor TA.Create;
begin
 inherited Create;
 f_SomeObject1 := TObject1.Create;
end;
 
...
 
constructor TB.Create;
begin
 inherited Create;
 f_SomeObject2 := TObject2.Create;
end;

This “style” is left to us by Borland who deceased in bose.

We have “become used” to it.

What is wrong with it?

Actually, it is “more correct” to write like this:

type
 TObject1 = class
 end;//TObject1
 
 TObject2 = class
 end;//TObject2
 
 TA = class
  private
   f_SomeObject1 : TObject1;
  public
   constructor Create;
 end;//TA
 
 TB = class(TA)
  private
   f_SomeObject2 : TObject2;
  public
   constructor Create;
 end;//TB
 
...
 
constructor TA.Create;
begin
 f_SomeObject1 := TObject1.Create;
 inherited Create;
end;
 
...
 
constructor TB.Create;
begin
 f_SomeObject2 := TObject2.Create;
 inherited Create;
end;

What have we done here?

We have SWAPPED “aggregated objects” initialization and “the call of inherited constructors”.

This is IMPORTANT.

Why?

The key word is - virtuality.

What do I mean?

For now let’s write in this way:

type
 TObject1 = class
  public
   SomeField : SomeDataType;
 end;//TObject1
 
 TObject2 = class
  public
   SomeField : SomeDataType;
 end;//TObject2
 
 TA = class
  private
   f_SomeObject1 : TObject1;
   f_SomeData : Integer;
  protected
   function CalcSomeData: Integer; virtual;
  public
   constructor Create;
 end;//TA
 
 TB = class(TA)
  private
   f_SomeObject2 : TObject2;
  protected
   function CalcSomeData: Integer; override;
  public
   constructor Create;
 end;//TB
 
...
 
constructor TA.Create;
begin
 inherited Create;
 f_SomeObject1 := TObject1.Create;
 f_SomeData := CalcSomeData;
end;
 
function TA.CalcSomeData: Integer;
begin
 Result := SomeAlgorythm1(f_SomeObject1.SomeField);
 // - everything is OK here
end;
 
...
 
constructor TB.Create;
begin
 inherited Create;
 f_SomeObject2 := TObject2.Create;
end;
 
function TB.CalcSomeData: Integer;
begin
 Result := SomeAlgorythm2(f_SomeObject2.SomeField);
 // - we get AV, because on call of CalcSomeData from constructor TA.Create the field f_SomeObject2 is not initialized
end;

What is to be done?

Now let’s write in this way:

type
 TObject1 = class
  public
   SomeField : SomeDataType;
 end;//TObject1
 
 TObject2 = class
  public
   SomeField : SomeDataType;
 end;//TObject2
 
 TA = class
  private
   f_SomeObject1 : TObject1;
   f_SomeData : Integer;
  protected
   function CalcSomeData: Integer; virtual;
  public
   constructor Create;
 end;//TA
 
 TB = class(TA)
  private
   f_SomeObject2 : TObject2;
  protected
   function CalcSomeData: Integer; override;
  public
   constructor Create;
 end;//TB
 
...
 
constructor TA.Create;
begin
 f_SomeObject1 := TObject1.Create;
 inherited Create;
 f_SomeData := CalcSomeData;
end;
 
function TA.CalcSomeData: Integer;
begin
 Result := SomeAlgorythm1(f_SomeObject1.SomeField);
 // - everything is OK here
end;
 
...
 
constructor TB.Create;
begin
 f_SomeObject2 := TObject2.Create;
 inherited Create;
end;
 
function TB.CalcSomeData: Integer;
begin
 Result := SomeAlgorythm2(f_SomeObject2.SomeField);
 // - everything is OK here because field f_SomeObject2 is initialized due to a MORE LATE call of constructor
end;

The problem has gone away.

Is the idea clear? http://programmingmindstream.blogspot.ru/2014/08/blog-post_64.html (in Russian)
I’ll note – “do not tell me about C++ and other languages”. They are organized in a different way http://ptgmedia.pearsoncmg.com/images/9780321334879/samplepages/0321334876.pdf (look at item 9, page 48).

Let me cite:

“It’s not a secret but a simple rule: virtual function is not virtual if it is called from constructor or destructor.

Rules should be learned by heart, which is inconvenient. To undersign the principle is more simple. In this case, the principle is in a cornerstone of inheritance implementation in C++: when creating the object, constructors in the hierarchy are called from base class up to the last inherited one. For destructors it is quite the reverse.

What do we get: class constructor always works with an assumption that descendant classes has not been yet created and, therefore, is has no right to call functions they define. As for virtual function, it has no choice but to call what it itself defines. It turns out that the virtual functions mechanism kind of does not work here. But it actually does not work since the table of descendant class virtual functions has not yet covered the current table.

It is quite the opposite for the destructor. Destructor “knows” that when it is called all the descendant classes have already been destructed and there’s nothing to call from them. For this reason it replaces the address of virtual functions table for its own table address and successfully calls virtual function version, which it itself defines.

So, virtual function is not virtual if it is called from constructor or destructor.”

Virtual functions in constructor or destructor  (in Russian)
That’s speaking about C++ and “other languages”.

BUT!

That is not all about Delphi.

Now, “pair of words” on “where this Borland’s style comes from”.

It “comes” from Turbo Pascal and Turbo Vision.

There constructors Init and the base object TObject were.

However, it had entirely DIFFERENT PARADIGM. I repeat – the PARADIGM. I use Caps Lock for a reason.

Once again I repeat – DIFFERENT PARADIGM.

What was there?

This:

constructor TObject.Init;
begin
 FillChar(@Self, InstanceSize, 0);
end;

Of course, InstanceSize – “looked differently”.

But I believe “the idea is clear”.

What happens with the descendant object?

We’ll write in this way:

type
 TA = object(TObject)
  public
   SomeField : Integer;
  public
   constructor Init;
 end;//TA
 
...
 
constructor TA.Init;
begin
 TObject.Init;
 SomeField := 123;
end;
 
...
var
 A : TA;
begin
 A := TA.Init;
 WriteLn(A.SomeField);
 // - here we get 123
end;

What about a “different paradigm”?

type
 TA = object(TObject)
  public
   SomeField : Integer;
  public
   constructor Init;
 end;//TA
 
...
 
constructor TA.Init;
begin
 SomeField := 123;
 TObject.Init;
end;
 
...
var
 A : TA;
begin
 A := TA.Init;
 WriteLn(A.SomeField);
 // - here we get 0
end;

Is the idea clear? http://programmingmindstream.blogspot.ru/2014/08/blog-post_64.html (in Russian)

“Many people” still “keep thinking” of this PARADIGM. At the same time, they somehow “forget” to call constructor from TObject.

I’ve seen such thing in A NUMBER of third-party libraries.

For example, in ImageEn.

The “discussion” about constructors/destructors, factories  (in Russian)  and"RAII"  – is not fully closed. It has just been opened.

Would you be interested, I’ll follow on it.

But “so far” that is “all I wanted to tell about constructors”.

About “where it came from” - Briefly. About triggering the exceptions

P.S. That is all due to the “one-eyed” architecture”  (in russian), but provides good “motivation for a special talk”.

P.P.S. Actually, the idea за the post was TRIVIAL - either “using of the factories” or “shattering of paradigm” . http://programmingmindstream.blogspot.ru/2014/09/blog-post_26.html?showComment=1412024065216#c6012843482459411429 (in Russian)
Although, “for some” simple “using factories” is already “shattering the paradigm”. Unfortunately.

Комментариев нет:

Отправить комментарий