pyscripter 694 Posted December 7, 2018 (edited) Marco Cantu presented in CodeRage 2018 an implementation of Smart Pointers based on generics, similar to the one found in Spring4D. It can be used as follows: var smartP: TSmartPointer<TStringList>; begin smartP := TStringList.Create; smartP.Value.Add('foo'); TStringList(smartP).Add('foo2'); I am using in my code a non-generics implementation from JclSysUtils var sl: TStringList; slSafeGuard: ISafeGuard; begin sl := TStringList(Guard(TStringList.Create, slSafeGuard)); sl.Add('foo'): The second has the advantage that you do not have to use the Value property to get the object at the cost of having an extra declaration and being less elegant (?). Are there any performance or other advantages to the Generics implementation? Isn't the non-generics implementation faster and doesn't it generate less code? Edited December 7, 2018 by pyscripter Typos Share this post Link to post
Stefan Glienke 2019 Posted December 7, 2018 (edited) The ISafeGuard approach is not a smartpointer but creating a scope to some resource. There is a important difference: a smart pointer combines the resource and its lifetime management into one entity. Also the record based approach is the most naive one - while Spring4D offers this one as well it has a more advanced one. As for performance: measure it yourself: program MeasureIt; {$APPTYPE CONSOLE} uses Spring, Diagnostics, Classes, SysUtils, JclSysUtils; const ADD_COUNT = 10; CREATE_COUNT = 100000; procedure MeasureSpring; var s: IShared<TStringList>; i: Integer; begin s := Shared.Make(TStringList.Create); for i := 1 to ADD_COUNT do s.Add(i.ToString); end; procedure MeasureJcl; var s: TStringList; g: ISafeGuard; i: Integer; begin s := TStringList.Create; Guard(s, g); for i := 1 to ADD_COUNT do s.Add(i.ToString); end; procedure MeasureClassic; var s: TStringList; i: Integer; begin s := TStringList.Create; try for i := 1 to ADD_COUNT do s.Add(i.ToString); finally s.Free; end; end; procedure Main; var sw: TStopwatch; i: Integer; begin sw := TStopwatch.StartNew; for i := 1 to CREATE_COUNT do MeasureSpring; Writeln(sw.ElapsedMilliseconds); sw := TStopwatch.StartNew; for i := 1 to CREATE_COUNT do MeasureJcl; Writeln(sw.ElapsedMilliseconds); sw := TStopwatch.StartNew; for i := 1 to CREATE_COUNT do MeasureClassic; Writeln(sw.ElapsedMilliseconds); end; begin Main; Readln; end. The implementation in Spring 1.2.2 (currently released version) uses very optimized code for the smart pointer itself avoiding the overhead of an object allocation and all the code associated with it but only allocates a 12 Byte (32bit) block with the IMT. Since IShared<T> is not a regular interface but an anonymous method you can directly access the members of the underlying type. Yes, there is a method call every time but as you can measure that does not cause any significant overhead (unless you call .Add a million times). And even then the actual work being performed totally outweighs the smart pointer overhead Edited December 7, 2018 by Stefan Glienke 4 1 Share this post Link to post
pyscripter 694 Posted December 7, 2018 (edited) As pointed out In another post by @Primož Gabrijelčič, in Delphi Rio you can just write: var s := Shared.Make(TStringList.Create); What an amazing hack Shared is! Also impressive type inference from Delphi. Edited December 8, 2018 by pyscripter 2 Share this post Link to post
pyscripter 694 Posted December 7, 2018 And for the record: Quote ADD_COUNT: 100 CREATE_COUNT: 100000 Spring Shared:699 Jcl: 648 Classic: 637 TSmartPointer: 752 1 Share this post Link to post
pyscripter 694 Posted December 7, 2018 One final remark. If performance is an issue with a "with" statement performance improves to classic levels i.e. procedure MeasureSpring; var i: Integer; begin var s := Shared.Make(TStringList.Create); with s() do for i := 1 to ADD_COUNT do Add(i.ToString); end; Share this post Link to post
Primož Gabrijelčič 223 Posted December 8, 2018 @pyscripter You don't need a temp variable at all: procedure MeasureSpring; var i: Integer; begin with Shared.Make(TStringList.Create)() do for i := 1 to ADD_COUNT do Add(i.ToString); end; Just ... with ... brrrr, terrible. This may also work (didn't test): procedure MeasureSpring; begin // something ... begin var s := Shared.Make(TStringList.Create)(); for var i := 1 to ADD_COUNT do s.Add(i.ToString); end; // something ... end; I would really like Embarcadero to enhance `with` block in that direction, so I could write: procedure MeasureSpring; begin with var s := Shared.Make(TStringList.Create)() do for var i := 1 to ADD_COUNT do s.Add(i.ToString); end; This should compile to the same code as my second example. (Except that at the moment it is not support.) 1 Share this post Link to post
Uwe Raabe 2064 Posted December 8, 2018 35 minutes ago, Primož Gabrijelčič said: Just ... with ... brrrr, terrible. Interestingly, that is quite similar to one of the reasons why "with" was invented in the first place: when accessing the record memory is costly. From Pascal User Manual and Report (emphasizes by me): Quote The with clause effectively opens the scope containing field identifiers of the specified record variable, so that the field identifiers may occur as variable identifiers. (Thereby providing an opportunity for the compiler to optimize the qualified statement.) 3 Share this post Link to post
Primož Gabrijelčič 223 Posted December 8, 2018 (edited) I ran a test in Rio and it works quite well: type TTestObj = class public constructor Create; destructor Destroy; override; end; constructor TTestObj.Create; begin inherited Create; Writeln('TTestObj.Create'); end; destructor TTestObj.Destroy; begin Writeln('TTestObj.Destroy'); inherited; end; procedure Test; begin Writeln('Test started'); begin var obj := Shared.Make(TTestObj.Create)(); Writeln('obj = ', obj.ClassName); Writeln('End of nested scope'); end; Writeln('Test completed'); end; Output: Test started TTestObj.Create obj = TTestObj End of nested scope Test completed TTestObj.Destroy Inline var object is not destroyed at the end of scope, but at the end of the method. Still, that's something I more or less expected. Other than that, the code works fine. For the record, this can also be achieved with the "traditional" syntax. Delphi will kindly keep the interface alive until the end of the method: procedure Test; var obj: TTestObj; begin Writeln('Test started'); obj := Shared.Make(TTestObj.Create)(); Writeln('obj = ', obj.ClassName); Writeln('Test completed'); end; And there's also a bit slower but shorter variation to create a shared object: Shared<TTestObj>.Make() Just saying 😉 Edited December 8, 2018 by Primož Gabrijelčič 1 1 Share this post Link to post
Dalija Prasnikar 1404 Posted December 8, 2018 30 minutes ago, Primož Gabrijelčič said: Inline var object is not created at the end of scope, but at the end of the method. Still, that's something I more or less expected. Other than that, the code works fine. You are wrong... object is destroyed at the end of the inner scope... but your output 'End of nested scope' is still executed within inner scope. Share this post Link to post
Primož Gabrijelčič 223 Posted December 8, 2018 3 minutes ago, Dalija Prasnikar said: You are wrong... object is destroyed at the end of the inner scope... but your output 'End of nested scope' is still executed within inner scope. Nope. Interface is cleared - and object destroyed - in the final `end` of the method. I verified with the debugger. Share this post Link to post
pyscripter 694 Posted December 8, 2018 @Primož Gabrijelčič If you do a minor change in Test (remove ()) procedure Test; begin Writeln('Test started'); begin var obj := Shared.Make(TTestObj.Create); Writeln('obj = ', obj.ClassName); Writeln('End of nested scope'); end; Writeln('Test completed'); end; Output: Test started TTestObj.Create obj = TTestObj End of nested scope TTestObj.Destroy Test completed Share this post Link to post
Primož Gabrijelčič 223 Posted December 8, 2018 Makes sense. In your code an 'inline var' is an interface of type `IShared<TTestObj>` and is destroyed at the end of the owning scope (begin..end). In my code, the 'inline var' is of type `TTestObj`. The `IShared<TTestObj>` interface is just something that was created during the execution and is owned by the method itself. Share this post Link to post
Dalija Prasnikar 1404 Posted December 8, 2018 2 minutes ago, Primož Gabrijelčič said: Nope. Interface is cleared - and object destroyed - in the final `end` of the method. I verified with the debugger. procedure Test; begin Writeln('Test started'); begin var obj := Shared.Make(TTestObj.Create)(); Writeln('obj = ', obj.ClassName); end; Writeln('End of nested scope'); Writeln('Test completed'); end; Test with above code, please. I am testing with plain TInterfacedObject and it works as expected... Share this post Link to post
pyscripter 694 Posted December 8, 2018 (edited) @Dalija Prasnikar I think @Primož Gabrijelčičmakes the point that the object is destroyed after 'Test Complete' e.g. procedure Test; begin Writeln('Test started'); begin var obj := Shared.Make(TTestObj.Create)(); Writeln('obj = ', obj.ClassName); end; Writeln('End of nested scope'); Writeln('Test completed'); end; Output: Test started TTestObj.Create obj = TTestObj End of nested scope Test completed TTestObj.Destroy I think this is because the result of Shared.Make(TTestObj.Create) which is a reference to function is not explicitly assigned to any variable so its scope is the function scope. The statement begin var obj := Shared.Make(TTestObj.Create)(); implicitly assigns the result of the function call to the obj whose type is TTestObj. Edited December 8, 2018 by pyscripter Share this post Link to post
Primož Gabrijelčič 223 Posted December 8, 2018 (edited) @Dalija Prasnikar Test started TTestObj.Create obj = TTestObj End of nested scope Test completed TTestObj.Destroy Edited December 8, 2018 by Primož Gabrijelčič Share this post Link to post
Primož Gabrijelčič 223 Posted December 8, 2018 2 minutes ago, pyscripter said: ...is not explicitly assigned to any variable so its scope is the function scope. Indeed. That's what I was trying to say four posts back 🙂 Share this post Link to post
Dalija Prasnikar 1404 Posted December 8, 2018 type TFoo = class(TInterfacedObject) public constructor Create; destructor Destroy; override; end; constructor TFoo.Create; begin inherited Create; Writeln('Foo Create'); end; destructor TFoo.Destroy; begin Writeln('Foo Destroy'); inherited; end; procedure Test; begin Writeln('procedure begin'); Writeln('before inner scope begin'); begin Writeln('after inner scope begin'); var ref: IInterface := TFoo.Create; Writeln('before inner scope end'); end; Writeln('after inner scope end'); Writeln('something'); Writeln('procedure end'); end; Results with following output procedure begin before inner scope begin after inner scope begin Foo Create before inner scope end Foo Destroy after inner scope end something procedure end Share this post Link to post
Primož Gabrijelčič 223 Posted December 8, 2018 As expected. I think we now all agree on how it works 🙂 1 Share this post Link to post
Dalija Prasnikar 1404 Posted December 8, 2018 1 minute ago, Primož Gabrijelčič said: Indeed. That's what I was trying to say four posts back 🙂 You are right... implicit variable hell There were some issues with scoping... so I went to check with simplest example... too simple I am afraid... Share this post Link to post
Emil Mustea 6 Posted December 8, 2018 (edited) To summarize: Who wants maximum performance like "classic" use it like this var obj := Shared.Make(TTestObj.Create)(); "obj" is object and will be free it at the end of the procedure; otherwise, use it: var obj := Shared.Make(TTestObj.Create); "obj" is interface and will be free it at the end of the block and every access has a small penalty of an anonymous method call. Edited December 9, 2018 by Emil Mustea 4 Share this post Link to post
Stefan Glienke 2019 Posted December 10, 2018 @Emil Mustea You could be that clever however the real runtime overhead of the spring smart pointer is so small that any such tricks are not necessary imo and rather lead to defects because of implicit variables as shown earlier. @pyscripter YMMV but when I compile with release config the results are within 2% difference. Depending on order and CPU caching in that micro-benchmark they are even equal. 1 Share this post Link to post
Arnaud Bouchez 407 Posted December 10, 2018 On 12/8/2018 at 9:19 AM, Primož Gabrijelčič said: I would really like Embarcadero to enhance `with` block in that direction, so I could write: Why not directly make it `using` and replace the `begin` `end` with `{ }` ? :) 1 1 1 2 Share this post Link to post
Rudy Velthuis 91 Posted January 3, 2019 (edited) On 12/7/2018 at 5:37 PM, pyscripter said: Marco Cantu presented in CodeRage 2018 an implementation of Smart Pointers based on generics, similar to the one found in Spring4D. It can be used as follows: var smartP: TSmartPointer<TStringList>; begin smartP := TStringList.Create; smartP.Value.Add('foo'); TStringList(smartP).Add('foo2'); I am using in my code a non-generics implementation from JclSysUtils var sl: TStringList; slSafeGuard: ISafeGuard; begin sl := TStringList(Guard(TStringList.Create, slSafeGuard)); sl.Add('foo'): The second has the advantage that you do not have to use the Value property to get the object at the cost of having an extra declaration and being less elegant (?). Are there any performance or other advantages to the Generics implementation? Isn't the non-generics implementation faster and doesn't it generate less code? Heh, I actually donated that SafeGuard code to JCL many years ago. They modified the name a little, but not the concept. It works, but I am not sure if all guarded objects will be released on an exception. You'll have to test that. The advantage is indeed that you don't have to cast or anything. You use the original object you created. Yesterday, I even wrote a better approach, using records an implicit and explict casts. That one is certain to work and does not need a cast and the pointer can be used as if it were the actual object (by implicit cast). type IObjectGuard<T: class> = interface function Value: T; end; TObjectGuard<T: class> = class(TInterfacedObject, IObjectGuard<T>) private FValue: T; public constructor Create(Obj: T); destructor Destroy; override; function Value: T; end; SmartPtr<T: class> = record private FGuard: IObjectGuard<T>; public class operator Explicit(const Obj: T): SmartPtr<T>; class operator Implicit(const Ptr: SmartPtr<T>): T; end; { TObjectGuard<T> } constructor TObjectGuard<T>.Create(Obj: T); begin FValue := Obj; end; destructor TObjectGuard<T>.Destroy; begin FValue.Free; inherited; end; function TObjectGuard<T>.Value: T; begin Result := FValue; end; { SmartPtr<T> } class operator SmartPtr<T>.Explicit(const Obj: T): SmartPtr<T>; begin Result.FGuard := TObjectGuard<T>.Create(Obj); end; class operator SmartPtr<T>.Implicit(const Ptr: SmartPtr<T>): T; begin if Ptr.FGuard <> nil then Result := Ptr.FGuard.Value else Result := nil; end; And it can be used like: var Bob, Fred, Jack: TTalking; begin Bob := SmartPtr<TTalking>(TTalking.Create('Bob')); Fred := SmartPtr<TTalking>(TTalking.Create('Fred')); Jack := SmartPtr<TTalking>(TTalking.Create('Jack')); Fred.Talk; Bob.Talk; raise Exception.Create('Error Message'); Jack.Talk; Writeln('This is the end of the routine'); end; As you can see, this also creates guards. They do work, even with the Exception. All are freed when the exception occurs. Edited January 3, 2019 by Rudy Velthuis 4 Share this post Link to post
pyscripter 694 Posted January 3, 2019 @Rudy VelthuisGood stuff. How can we have a look at your new code? Share this post Link to post