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

Briefly. Some more "reasoning about RAII"

Original in Russian: http://programmingmindstream.blogspot.ru/2014/09/raii.html

Based on - RAII

I’d like to write about “integral objects”.

I mean, the objects that create other objects internally.

Usually it is done in this way:

type
 TSomeClass1 = class
  public
   constructor Create(aSomeData1 : TSomeType1);
 end;//TSomeClass1
 
 TSomeClass = class
  private
   f_SomeClass1 : TSomeClass1;
   f_SomeClass2 : TSomeClass2;
  public
   constructor Create(aSomeData1 : TSomeType1; aSomeData2: TSomeType2);
   destructor Destroy; override;
 end;//TSomeClass
 
...
 
constructor TSomeClass1.Create(aSomeData1 : TSomeType1);
begin
 Assert(IsValid(aSomeData1));
 inherited Create;
 ...
end;
 
...
 
constructor TSomeClass.Create(aSomeData1 : TSomeType1; aSomeData2: TSomeType2);
begin
 inherited Create;
 f_SomeClass1 := TSomeClass1.Create(aSomeData1);
 SomeInitCode;
 f_SomeClass2 := TSomeClass2.Create(aSomeData2);
end;
 
destructor TSomeClass.Destroy;
begin
 FreeAndNil(f_SomeClass2);
 SomeDoneCode;
 FreeAndNil(f_SomeClass1);
 inherited;
end;

But sometimes it can be done like this:

type
 TSomeClass = class
  private
   f_SomeClass1 : TSomeClass1;
   f_SomeClass2 : TSomeClass2;
  protected
   constructor Make(aSomeClass1 : TSomeClass1; aSomeClass2: TSomeClass2);
  public
   class function Create(aSomeData1 : TSomeType1; aSomeData2: TSomeType2): TSomeClass;
   destructor Destroy; override;
 end;//TSomeClass
 
constructor TSomeClass.Make(aSomeClass1 : TSomeClass1; aSomeClass2: TSomeClass2);
begin
 inherited Create;
 f_SomeClass1 := aSomeClass1;
 SomeInitCode;
 f_SomeClass2 := aSomeClass2;
end;
 
class function TSomeClass.Create(aSomeData1 : TSomeType1; aSomeData2: TSomeType2): TSomeClass;
var
 l_SomeClass1: TSomeClass1;
 l_SomeClass2: TSomeClass2;
begin
 Assert(IsValid(aSomeData1));
 Assert(IsValid(aSomeData2));
 l_SomeClass1 := TSomeClass1.Create(aSomeData1);
 l_SomeClass2 := TSomeClass2.Create(aSomeData2);
 Result := Make(l_SomeClass1, l_SomeClass2);
end;
 
destructor TSomeClass.Destroy;
begin
 FreeAndNil(f_SomeClass2);
 SomeDoneCode;
 FreeAndNil(f_SomeClass1);
 inherited;
end;

What is “delicious”?

The instance of TSomeClass will not be created until the instances of TSomeClass1 and TSomeClass2 are created.

It means, the destructor will not be called.

Therefore, SomeInitCode won’t be called either.

Hence, there will be no problems with destructing of a partially created object.

There are questions about SomeDoneCode. It will be called (we look into documentation).

But!

If it depends only from the objects initialized above, there will be no problems either.

Besides, it will be called only if the lines:

...
 l_SomeClass1 := TSomeClass1.Create(aSomeData1);
 l_SomeClass2 := TSomeClass2.Create(aSomeData2);
...

- are executed.

And the lines:

...
 inherited Create;
 f_SomeClass1 := aSomeClass1;
...

- do not pass for some reason.

But in this area “the probability tends to zero”.

We can also write in this way:

type
 TSomeClass = class
  private
   f_SomeClass1 : TSomeClass1;
   f_SomeClass2 : TSomeClass2;
  protected
   constructor Make(aSomeClass1 : TSomeClass1; aSomeClass2: TSomeClass2);
  public
   class function Create(aSomeData1 : TSomeType1; aSomeData2: TSomeType2): TSomeClass;
   destructor Destroy; override;
 end;//TSomeClass
 
constructor TSomeClass.Make(aSomeClass1 : TSomeClass1; aSomeClass2: TSomeClass2);
begin
 inherited Create;
 f_SomeClass1 := aSomeClass1;
 SomeInitCode;
 f_SomeClass2 := aSomeClass2;
end;
 
class function TSomeClass.Create(aSomeData1 : TSomeType1; aSomeData2: TSomeType2): TSomeClass;
var
 l_SomeClass1: TSomeClass1;
 l_SomeClass2: TSomeClass2;
begin
 l_SomeClass1 := TSomeClass1.Create(aSomeData1);
 l_SomeClass2 := TSomeClass2.Create(aSomeData2);
 Result := Make(l_SomeClass1, l_SomeClass2);
end;
 
destructor TSomeClass.Destroy;
begin
 FreeAndNil(f_SomeClass2);
 if (f_SomeClass1 <> nil) then
 // - we check if f_SomeClass1 is initialized
 // Why do we need to check? The answer is – “otherwise, why is this SomeDoneCode exactly HERE?
  SomeDoneCode;
 FreeAndNil(f_SomeClass1);
 inherited;
end;

Or even in this way (for paranoiacs like me):

type
 TSomeClass = class
  private
   f_SomeClass1 : TSomeClass1;
   f_SomeClass2 : TSomeClass2;
  protected
   constructor Make(aSomeClass1 : TSomeClass1; aSomeClass2: TSomeClass2);
  public
   class function Create(aSomeData1 : TSomeType1; aSomeData2: TSomeType2): TSomeClass;
   destructor Destroy; override;
 end;//TSomeClass
 
constructor TSomeClass.Make(aSomeClass1 : TSomeClass1; aSomeClass2: TSomeClass2);
begin
 inherited Create;
 f_SomeClass1 := aSomeClass1;
 SomeInitCode(f_SomeClass1);
 f_SomeClass2 := aSomeClass2;
end;
 
class function TSomeClass.Create(aSomeData1 : TSomeType1; aSomeData2: TSomeType2): TSomeClass;
var
 l_SomeClass1: TSomeClass1;
 l_SomeClass2: TSomeClass2;
begin
 l_SomeClass1 := TSomeClass1.Create(aSomeData1);
 l_SomeClass2 := TSomeClass2.Create(aSomeData2);
 Result := Make(l_SomeClass1, l_SomeClass2);
end;
 
destructor TSomeClass.Destroy;
begin
 FreeAndNil(f_SomeClass2);
 if (f_SomeClass1 <> nil) then
 // - we check if f_SomeClass1 is initialized
 // Why do we need to check? The answer is – “otherwise, why is this SomeDoneCode exactly HERE?
  SomeDoneCode(f_SomeClass1);
 FreeAndNil(f_SomeClass1);
 inherited;
end;

- probably, it is more clear this way?

Finally, TSomeClass1.Create and TSomeClass2.Create can be created “inductively” if they are also integral.

We should not forget that if constructor throws an exception, the destructor is definitely called (it is also written in documentation), even on a “partially initialized object”.

I’ll repeat once more:

If constructor throws an exception, the destructor is called definitely - even on a “partially initialized object”.

It should be taken into account.

For this reason I had a post - http://programmingmindstream.blogspot.ru/2014/09/blog-post_9.html

What do I lead to?

The “smaller” our objects are, the “smaller” the probability to have destructor call on “partially initialized object” is.

I’ll repeat:

The “smaller” our objects are, the “smaller” the probability to have destructor call on “partially initialized object” is.

It means working less in the constructor instead of factory provides more “peace” for us, as well as decreases the probability of getting AccessViolation or/and unreleased resources.

I’ll be glad if my thoughts will be helpful for anybody.

I’ve used them myself. Several times. But, that is not a measure.

If you’re actually interested, I could try to follow using a real example.

P.S. What we got using this approach is that, on the one hand, we kind of encapsulate “inner logic” in class and, on the other hand, avoid creating a “partially initialized object instance”.

In general, I personally like factories, mixins and Assert’s for a while now. It seems to be serious and long-lasting.


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

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