Marco Cantu 78 Posted November 7, 2018 Just wanted to share my last blog post on changes to the Delphi language in 10.3: http://blog.marcocantu.com/blog/2018-november-custom-managed-records-delphi.html Still a bit rough in some details, but a very handy feature overall. 6 2 Share this post Link to post
Sherlock 663 Posted November 7, 2018 Awesome, but this Quote There is of course the risk, depending on how you write the code, that the default constructor gets invoked twice, implicitly by the compiler and in your code. In case of inline variable declarations, this won’t happen, but in other cases it might. sounds ominous. Could you please elaborate? Share this post Link to post
Markus Kinzler 174 Posted November 7, 2018 On one side EMBT removes "automatic" management for classes (ARC) on the other hand they introduce automatisms for "static" types. It might be dangerous. Is there a way to off switch this (useful) feature? Share this post Link to post
Sherlock 663 Posted November 7, 2018 It would seem that by not providing constructors/destructors/assign you would get the "old" behavior. Share this post Link to post
Emil Mustea 6 Posted November 7, 2018 I think if we use copy constructor we can create smart pointers like this: procedure DoSomething(OtherParameters: string; New: TAutoFreeRecord); //here "New" record is created by copy constructor var s: TStrings; begin s := New.Of(TStringList.Create); s.Add('one'); s.Add('two'); //here "New" goes out of scope and will destroy "s" (custom destructor) end; or with inline declaration: procedure DoSomething(OtherParameters: string; New: TAutoFreeRecord); //here "New" record is created by copy constructor begin s := New.Of(TStringList.Create); s.Add('one'); s.Add('two'); //here "New" goes out of scope and will destroy "s" (custom destructor) end; It was very nice if managed records in 10.3 had member lifting. In that way we could escape declaring "New"; New.Of will return instead a record which the only member could be lifted and used as a TStrings. Share this post Link to post
Rudy Velthuis 91 Posted November 7, 2018 1 hour ago, Markus Kinzler said: On one side EMBT removes "automatic" management for classes (ARC) on the other hand they introduce automatisms for "static" types. It might be dangerous. Is there a way to off switch this (useful) feature? They don't intrduce automatisms you don't want. In fact, they now let you customize or even avoid the already existing automatisms (e.g. for records with managed content), allowing for better optimization. At the moment, normal, so called plain records (i.e. not M-records) are not managed. But records with managed content are already automatically managed using the usual (hidden) functions System._InitializeRecord, System_CopyRecord and System._FinalizeRecord. That management always takes place, and there (was) no way to get out of that. But these routines are slow and use TypeInfo. They must walk through that type info to determine which members must be initialized, refcounted, finalized, etc. at what moment. That takes time. With the new M-records, you can replace these routines by declaring a (default) constructor without parameters, a (default) destructor and a new Assign operator. These *replace* _InitializeRecord, _CopyRecord and _FinalizeRecord. So now you can selectively only initialize/copy/finalize what needs to be done. This saves time, as your code doesn't have to slowly walk/search the typeinfo to find what must be done for each member field. This is a possibility for manual optimization many people (including me) have asked for already. And it opens up new possibilities, like e.g. RAII. If you combine it with inline declared, block local variables, there are many possibilities. 1 1 Share this post Link to post
Rudy Velthuis 91 Posted November 7, 2018 18 minutes ago, Emil Mustea said: I think if we use copy constructor we can create smart pointers like this: procedure DoSomething(OtherParameters: string; New: TAutoFreeRecord); //here "New" record is created by copy constructor var s: TStrings; begin s := New.Of(TStringList.Create); s.Add('one'); s.Add('two'); //here "New" goes out of scope and will destroy "s" (custom destructor) end; or with inline declaration: procedure DoSomething(OtherParameters: string; New: TAutoFreeRecord); //here "New" record is created by copy constructor begin s := New.Of(TStringList.Create); s.Add('one'); s.Add('two'); //here "New" goes out of scope and will destroy "s" (custom destructor) end; It was very nice if managed records in 10.3 had member lifting. In that way we could escape declaring "New"; New.Of will return instead a record which the only member could be lifted and used as a TStrings. That should be begin var s := New.Of(TStringList.Create); Note that Of could be an error (keyword), although as New.Of it might compile. You could give TAutoFreeRecord a class function returning an instance of the record: begin var S := TAutoFreeRecord.New(TStringList.Create); // returns TStringList instance Share this post Link to post
Emil Mustea 6 Posted November 7, 2018 31 minutes ago, Rudy Velthuis said: That should be begin var s := New.Of(TStringList.Create); Note that Of could be an error (keyword), although as New.Of it might compile. You could give TAutoFreeRecord a class function returning an instance of the record: begin var S := TAutoFreeRecord.New(TStringList.Create); // returns TStringList instance 1. I get to used with inline declaration 🙂 2. You wrote "instance of a record" - in this case we need member lifting. If TAutoFreeRecord returns generic type of that parameter, then it's like the example I gave. Share this post Link to post
Guest Posted November 7, 2018 I think i understand this thingy now! Thanks @Marco Cantu, that was a pretty down-to-the-facts post. Share this post Link to post
Rudy Velthuis 91 Posted November 7, 2018 1 hour ago, Emil Mustea said: 2. You wrote "instance of a record" - in this case we need member lifting. If TAutoFreeRecord returns generic type of that parameter, then it's like the example I gave. Wha exactly do you mean with "member lifting"? I only know the term in association with nullable types. Share this post Link to post
Emil Mustea 6 Posted November 7, 2018 32 minutes ago, Rudy Velthuis said: Wha exactly do you mean with "member lifting"? I only know the term in association with nullable types. instead of writing s.Value.Add('one'), you write s.Add('one'), where "s" is a record but point to the only member of that record, which is in our case a TStringList. So you have member lifting. Share this post Link to post
Markus Kinzler 174 Posted November 7, 2018 @Rudy Velthuis I think you misunderstood me. My concern are only about the automatic invocation. Quote The huge difference between this new constructor and what was previously available for records is the automatic invocation. In fact if you write something like: procedure TForm5.btnMyRecordClick(Sender: TObject); var my1: TMyRecord; begin Log (my1.Value.ToString); end; you’ll end up invoking both the default constructor and the destructor, and you’ll end up with a try-finally block generated by the compiler for your managed record instance. Share this post Link to post
Dalija Prasnikar 1396 Posted November 7, 2018 1 hour ago, Markus Kinzler said: @Rudy Velthuis I think you misunderstood me. My concern are only about the automatic invocation. Automatic invocation is the whole point of managed records. If they have constructors and destructors, you want them to run. Yes, you can do that manually, but then they would not be called managed records. If you don't want managed records, and you want to perform some initialization and finalization code manually, you can use plain record procedures. You don't need constructors and destructors in that case. 2 Share this post Link to post
David Heffernan 2345 Posted November 8, 2018 19 hours ago, Markus Kinzler said: On one side EMBT removes "automatic" management for classes (ARC) on the other hand they introduce automatisms for "static" types. It might be dangerous. Is there a way to off switch this (useful) feature? There's nothing to switch off. You choose to switch it on on a type by type basis. 2 Share this post Link to post
Kryvich 165 Posted November 14, 2018 (edited) At today's webinar, new custom managed records in Delphi have been shown. There was such a declaration: And the implementation of operator Assign: class operator TMyRecord.Assign(var Dest: TMyRecord; var Src: TMyRecord); begin Dest.Value := Src.Value; end; Is there an error here? Perhaps it would be more correct to write const Src: TMyRecord? Because the above declaration allows you to change the Src variable: Src.Value := Dest.Value; Even better it would be to write it as class operator TMyRecord.Assign(const Src: TMyRecord): TMyRecord; begin Result.Values := Src.Value; end; And prohibit to change the variable Result itself (it's preallocated). Edited November 15, 2018 by Kryvich Share this post Link to post
Cristian Peța 103 Posted November 15, 2018 Nice if we optionally can modify the source. For example we can mark that a copy was made or to increment a counter of copies. Share this post Link to post
Kryvich 165 Posted November 15, 2018 (edited) @Cristian Peța Yes, but the source can be constant, or a constant parameter of a method. procedure TestRec(const DoNotChangeMe: TManagedRecord); begin ... var tempRec := DoNotChangeMe; ... end; Edited November 15, 2018 by Kryvich Share this post Link to post
Uwe Raabe 2057 Posted November 15, 2018 It is still possible to change the fields of a const record parameter when you use one of its own methods to do that. Share this post Link to post
Kryvich 165 Posted November 15, 2018 @Uwe Raabe True remark. It is important that the source variable itself is not changed, so it must be declared as const. Share this post Link to post
David Heffernan 2345 Posted November 15, 2018 1 hour ago, Uwe Raabe said: It is still possible to change the fields of a const record parameter when you use one of its own methods to do that. This is still an enormous weakness in the language. Share this post Link to post
Guest Posted November 15, 2018 (edited) 1 hour ago, Uwe Raabe said: It is still possible to change the fields of a const record parameter when you use one of its own methods to do that. So you would say this code type TTestRec = record Value: Integer; procedure Change( AValue: Integer ); end; { TTestRec } procedure TTestRec.Change( AValue: Integer ); begin Self.Value := AValue; end; procedure Test1( const rec: TTestRec ); begin // will not compile // rec.Value := 1; end; procedure Test2( const rec: TTestRec ); begin rec.Change( 2 ); end; procedure Test3( rec: TTestRec ); begin rec.Value := 3; end; procedure Test4( rec: TTestRec ); begin rec.Change( 4 ); end; procedure Test5( var rec: TTestRec ); begin rec.Value := 5; end; procedure Test6( var rec: TTestRec ); begin rec.Change( 6 ); end; procedure Main; var rec: TTestRec; begin rec.Value := 0; Test1( rec ); Writeln( rec.Value ); Test2( rec ); Writeln( rec.Value ); Test3( rec ); Writeln( rec.Value ); Test4( rec ); Writeln( rec.Value ); Test5( rec ); Writeln( rec.Value ); Test6( rec ); Writeln( rec.Value ); end; begin try Main( ); except on E: Exception do Writeln( E.ClassName, ': ', E.Message ); end; Readln; end. should produce the following result? 0 2 <-- const record modified by method 0 0 5 6 Well, actually I get 0 0 0 0 5 6 I can only change the var records. Edited November 15, 2018 by Guest Share this post Link to post
Lars Fosdal 1791 Posted November 15, 2018 Another variation type PTestRec = ^TTestRec; procedure Test7( const rec: PTestRec ); begin rec.Change( 7 ); end; ... Test7( @rec ); Writeln( rec.Value ); shows 7 Share this post Link to post
Guest Posted November 15, 2018 (edited) @Lars Fosdal With a const argument you cannot change the argument itself. The argument in this case is the Pointer and not the record. This is nearly the same as passing the record as var argument. procedure foo( arg ) // we can change arg, but it is a copy procedure foo( const arg ) // we cannot change arg procedure foo( var arg ) // we can change arg But you should always be aware what arg really is (f.i. reference type it is the reference) Edited November 15, 2018 by Guest Share this post Link to post
Lars Fosdal 1791 Posted November 15, 2018 True. My point was that relying on the const keyword as "security against tampering" is - as David also points out - a bit of a gamble. Share this post Link to post
Guest Posted November 15, 2018 2 minutes ago, Lars Fosdal said: True. My point was that relying on the const keyword as "security against tampering" is - as David also points out - a bit of a gamble. Well, if you pass a pointer to memory from your own process you should not be surprised that you can modify the memory you are pointing at. I would be surprised if I could not. Share this post Link to post