[fpc-devel] Pascal Smart Pointers Idea + ARC implementation

Maciej Izak hnb.code at gmail.com
Sat Oct 10 18:19:20 CEST 2015


First of all there was small bug in my previous code: wrong usage of
Finalize, lack of SmartFinalize and bad parameter for Implicit (keep in
mind that this structure will be complemented for TWeakPtr). Correct
version:

======begin code======
type
  TSmartPtr<T> = record
    // similar as overloading [] operators for property x[v: string]:
integer read gx write sx; default;
    Instance: T; default; // default keyword for non property, can be used
only for field of pointer type.
    RefCount: PLongint;

    procedure SmartFinalize();

    class operator Initialize(var aRec: TSmartPtr<T>);
    class operator Finalize(var aRec: TSmartPtr<T>);
    class operator Copy(constref aSource: TSmartPtr<T>; var aDest:
TSmartPtr<T>);

    // implicit or explicit operator should be used before "default" field
    operator Implicit(const aValue: T); // special version of
Implicit/Explicit is also needed (available only when is used default for
field)
    operator Explicit: TRawSmartPtr;
  end;

procedure TSmartPtr<T>.SmartFinalize();
begin
  if RefCount <> nil then
    if InterLockedDecrement(RefCount^)=0 then
    begin
      Dispose(RefCount);
      Dispose(Instance);
    end;
end;

class operator TSmartPtr<T>.Initialize(var aRec: TSmartPtr<T>);
begin
  aRec.RefCount := nil;
end;

class operator TSmartPtr<T>.Finalize(var aRec: TSmartPtr<T>);
begin
  aRec.SmartFinalize();
end;

class operator TSmartPtr<T>.Copy(constref aSource: TSmartPtr<T>; var aDest:
TSmartPtr<T>);
begin
  if aDest.RefCount <> nil then
    Finalize(aDest);
  if aSource.RefCount <> nil then
    InterLockedIncrement(aSource.RefCount^);
  aDest.RefCount := aSource.RefCount;
  aDest.Instance := aSource.Instance;
end;

operator TSmartPtr<T>.Implicit(const aValue: T);
begin
  if aDest.RefCount <> nil then
    aDest.SmartFinalize();

  New(RefCount);
  RefCount^ := 0;

  InterLockedIncrement(RefCount^);
  Instance := aValue;
end;

operator TSmartPtr<T>.Explicit: TRawSmartPtr;
begin
  Result.RefCount := RefCount;
  Result.Instance := Instance;
end;
======end code======

2015-10-10 9:59 GMT+02:00 Sven Barth <pascaldragon at googlemail.com>:

> Would you please explain why you'd need this raw structure besides for
> printing the RecCounts? (honest question, maybe I'm still too tired ;) )
>
> There is no way to access RecCount from TSmartPtr. When is used "default"
inside TSmartPtr (or inside any other record), TSmartPtr works only as
proxy object to "Instance" field. (!) but to perform functionality is
necessary to treat Implicit and Explicit operators as operators with higher
priority than "default" field, an example:

===begin code===
var
  x: TSmartPtr<PInteger>;
  i: Integer;
begin
  x := New(PInteger); // Implicit operator instead of default "Instance"
field (the operator Implicit has precedence)
  WriteLn(x^); // default field "Instance" is used, will print some random
number
  x := 'foo'; // Error: Incompatible types: got "Constant String" expected
"PInteger" (no Implicit operator found so is used  default "Instance" field
  i := x.RecCount; // Error: Illegal qualifier (no implicit operator found
so is used default "Instance" field)
  TSmartPtr<PInteger>(x).Instance^ := 10; // Error: Illegal type
conversion: "PInteger" to TSmartPtr<PInteger> (no Explicit operator found
for TSmartPtr so is used proxy default "Instance" field.
  // Btw Explicit nor Implicit operator has no sense here because "default"
field will be always block access for other record structures)
  // in that situation we need raw structure with similar memory/structures
layout

  TRawSmartPtr(x).Instance^ := 5; // Explicit operator instead of default
"Instance" field (the operator Explicit has precedence)
end;
===end code===

!!! WARNING -> Explicit and Implicit in that case is different than
standard Explicit / Implicit. To be precise we have two new operators:

===begin code===
operator Explicit: A;
operator Implicit(const Value: B);
// there is no class keyword before declaration, look at example
implementation of TSmartPtr
===end code===

these two operators have a higher priority than "default" field, and
"default" field have higher priority than any other
field/property/operator/method/const/type inside record structure. When
"default" is used for field then we don't have direct access to record. New
variant of Explicit operator and raw structure is IMO elegant way to get
access for original record fields.

> This is where we reach the dangerous territory ;)
> The problem with "default" is how to differentiate from a normal field
> usage of the record? Keep in mind that a feature might be used in different
> contexts than one first imagined. So how to ensure that one can access
> field X of the record and not field X of the default?
>
I know it is risky :P. In attached proposition, problem of overloading
operators like "@", ".", "^" is omitted. You need to start thinking about
"default" as proxy. Mentioned problem don't exist because programmer can
access only "Instance" field (but not directly, for this is used "compiler
magic"). The only place where exist access to record structures is
implementation of this record. From implementation is possible access to
any field or property. Inside implementation of record with "default"
field, "default" field has no effect:

===begin code===
type
  TFoo = record
    { ... }
    a: PInteger; default;
    property x[v: string]: integer read gx write sx; default;

    procedure Foo;
  end;

procedure TFoo.Foo()
begin
  Self['x'] := 10; // is ok
end;

var
  f: TFoo;
begin
  f['x'] := 10; // equivalent of f.a['x'] := 10;
  // ^^^ Error: Incompatible types: got "Constant String" expected
"LongInt"
===end code===

> Also for classes: is Instance a ^T as well or merely a T?
>
You are right, my bad. I was tired :P. We need only "T" even for TSmartPtr.

> I suppose Finalize here calls TSmartPtr<>.Finalize and not
> System.Finalize? We don't usually have directly callable operators, so keep
> that in mind.
>
> Right. Fixed on top this message ;).

> Only behind a modeswitch or maybe even a mode Oxygene please. And take
> care with prefixes, they are a PITA to handle as I've already told you ;)
>
> do not scare me!

Wit presented syntax is possible to create many variants of Smart Pointers
with different functionality. Influence of "default" field can may be
weakened by introducing new operators like new Implicit / Explicit (for
example operator TypeInfo).

Few more examples:

var
  a, b: TSmartObj<TObject>;
begin // TSmartObj<TObject>.Initialize(a); TSmartObj<TObject>.Initialize(b);
  a := TObject.Create; // a.Implicit(TObject.Create);
  b := a; // TSmartObj<TObject>.Copy(a, b);
  FreeAndNil(a); // FreeAndNil(a.Instance);
  WriteLn(a = b);// a.Instance = b.Instance // print false
  // we need to clear b
  b := nil; // b.Instance := nil;
  WriteLn(TypeInfo(a) = TypeInfo(TSmartObj<TObject>)) //
TypeInfo(a.Instance) = TypeInfo(TSmartObj<TObject>) // print false

-- 
Best regards,
Maciej Izak
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.freepascal.org/pipermail/fpc-devel/attachments/20151010/ef19ba65/attachment.html>


More information about the fpc-devel mailing list