Jump to content
Lars Fosdal

Generics: Classes vs Records - Differences in use

Recommended Posts

You are simplifying things here - yes some collections such as list<T> can easily give ref access to its items but other collections might not.

Working around the fact that you are dealing with value type semantics by accessing items by ref (or even giving raw access to the storage array such as the List property in the RTL TList<T> does) is borderline imo.

  • Like 1

Share this post


Link to post

It is not very difficult to make a generic list for records that allows manipulating the record content directly:

type
  TChangeableRecordList<T: record> = class(TList<T>)
  type
    PT = ^T;
  private
    function GetItem(Index: Integer): PT;
    procedure SetItem(Index: Integer; const Value: PT);
  public
    property Items[Index: Integer]: PT read GetItem write SetItem; default;
  end;

implementation

function TChangeableRecordList<T>.GetItem(Index: Integer): PT;
begin
  Result := Addr(PList^[Index]);
end;

procedure TChangeableRecordList<T>.SetItem(Index: Integer; const Value: PT);
begin
  inherited Items[Index] := Value^;
end;

A suitable test case could look like this:

type
  TMyRec = record
    IntValue: Integer;
  end;

var
  list: TChangeableRecordList<TMyRec>;
  myRec: TMyRec;
begin
  list := TChangeableRecordList<TMyRec>.Create;
  try
    myRec.IntValue := 1;
    list.Add(myRec);
    Assert(list[0].IntValue = 1);
    list[0].IntValue := 3;
    Assert(list[0].IntValue = 3);
  finally
    list.Free;
  end;
end;

 

  • Like 1

Share this post


Link to post

In my experience, it is far easier to use classes when you need to modify contents.

I wish it weren't so, and it can be worked around - but then usually with pointer references, and at that point (pun intended), I might as well be doing objects and maintain type safety and support polymorphism.

 

Share this post


Link to post
14 minutes ago, Stefan Glienke said:

And there the trouble starts - why on earth should the byref property be writeable.

Yes, this should be a read only property.

 

In an ideal world we'd have proper language support for references, as I think is especially we done in D

 

foreach (ref elem; arr) {
    elem = 0;
}

 

  • Like 1

Share this post


Link to post
3 hours ago, Stefan Glienke said:

And there the trouble starts - why on earth should the byref property be writeable.

Why not?

Share this post


Link to post
40 minutes ago, Fr0sT.Brutal said:

Why not?

Because the API now provides read by ref access which already makes it possible to changes values (which was the point) but also set by ref which does not store the ref but dereferences and assigns it.

Also the regular Items property is now hidden and cannot be used. While it might be the easy solution it's not a good one in my book. In fact I think there is not even a good one unless language support.

 

What prevents anyone from keeping around a reference that they retrieved from the Items property and then something in the list changes and boom.

Share this post


Link to post
12 hours ago, David Heffernan said:

TList<T>

The RTL's TList<T> class does not provide access to elements by reference in the TList<T>.Items[] property, only access by value.  You would have to use the TList<T>.List property instead, which gives you access to the underlying dynamic array so you can access the raw element data directly.

 

Edited by Remy Lebeau

Share this post


Link to post
55 minutes ago, Remy Lebeau said:

The RTL's generic TList<T> does not provide access to elements by reference, only by value.  At least for its Items[] property.  Its List property gives you access to the underlying dynamic array, so you can access the elements directly.

 

Your third sentence directly contradicts the first sentence. 

Share this post


Link to post
4 hours ago, David Heffernan said:

Your third sentence directly contradicts the first sentence. 

Not when you take the 2nd sentence into account.  But whatever.  I reworded my previous comment, just for you.

Share this post


Link to post
14 hours ago, Stefan Glienke said:

What prevents anyone from keeping around a reference that they retrieved from the Items property and then something in the list changes and boom.

Isn't that just the same with classes?

 

Of course one has to know what can be done and what should be avoided. That is no difference to the standard generic record list, where you need to know that you are changing content of copies of records and not that of the records itself.

 

My approach (which may be more a proof of concept than a solution for production) allows to work with a generic record list just like with the standard one, with the feature to change the record contents in the list, just like it was a simple array of records. I am pretty sure that it will work for the majority of use cases. Whenever the language gets a new feature that makes this obsolete - so then.

 

It is near to impossible (and probably not even desirable) to make everything fool proof. IMHO fools shouldn't be allowed to write programs in the first place.

Share this post


Link to post
54 minutes ago, Uwe Raabe said:

Isn't that just the same with classes?

Nope - with classes you are dealing with reference types - in this case you are pointing to a location inside the dynamic array being used as storage inside the list - any reallocation can change that.

 

56 minutes ago, Uwe Raabe said:

It is near to impossible (and probably not even desirable) to make everything fool proof.

True - however there is a difference between a language level feature and simply giving out some pointers to memory not directly under your control.

ref return in C# for example has very strict rules to avoid running into any kind of problems.

Share this post


Link to post
1 hour ago, Stefan Glienke said:

Nope - with classes you are dealing with reference types - in this case you are pointing to a location inside the dynamic array being used as storage inside the list - any reallocation can change that.

In a collection which owns its members then removing an item leads to a stale reference in either scenario (classes vs records). 

Share this post


Link to post
17 minutes ago, David Heffernan said:

In a collection which owns its members then removing an item leads to a stale reference in either scenario (classes vs records). 

If I got that right, Stefan is referring to the case where the array is relocated, which invalidates the record pointers. This is not the case for a class list, where only pointers to the class instances are stored inside the array. Relocating such an array will keep the instance pointers intact.

Share this post


Link to post
1 hour ago, Uwe Raabe said:

If I got that right, Stefan is referring to the case where the array is relocated, which invalidates the record pointers. This is not the case for a class list, where only pointers to the class instances are stored inside the array. Relocating such an array will keep the instance pointers intact.

Granted the indirection that is offered by a reference type does make some of the issues hard to trip over, but they still exist.

Share this post


Link to post
1 minute ago, David Heffernan said:

Granted the indirection that is offered by a reference type does make some of the issues hard to trip over, but they still exist.

It is possible to overcome some of the issues by implementing a notification mechanism. Much like it is in any TForm for components it owns.

Share this post


Link to post
21 hours ago, Stefan Glienke said:

What prevents anyone from keeping around a reference that they retrieved from the Items property and then something in the list changes and boom. 

Ah yes right I haven't noticed the getter returning pointer to array item. In my record list class I use dynamically allocated records and store pointers only so have no such issues.

Share this post


Link to post
1 hour ago, Fr0sT.Brutal said:

Ah yes right I haven't noticed the getter returning pointer to array item. In my record list class I use dynamically allocated records and store pointers only so have no such issues.

Well, you avoid some of the issues, but not all. For instance you don't avoid the issue where an item is deleted, but a stale pointer is retained. Additionally you end up with a large number of heap allocations, and memory that can be scattered which can impact performance.

Share this post


Link to post
23 hours ago, David Heffernan said:

Well, you avoid some of the issues, but not all. For instance you don't avoid the issue where an item is deleted, but a stale pointer is retained. Additionally you end up with a large number of heap allocations, and memory that can be scattered which can impact performance.

Yes I know that but that's how things are. If someone's going to shoot himself in the leg, even the most safe code couldn't prevent it. I had no intention of creating a fool-proof class without any real necessity. TList<TObject> has all the same weaknesses but I haven't seen anyone complaining about them.

As for allocations, they're not so massive and quite acceptable compared to array-of-large-blocks allocations and moves when the list is sorted.

Share this post


Link to post
4 minutes ago, Fr0sT.Brutal said:

As for allocations, they're not so massive and quite acceptable compared to array-of-large-blocks allocations and moves when the list is sorted.

That trade off depends on the size of the record. For small records then performance is better if the items are stored directly in a contiguous array.

Share this post


Link to post
6 minutes ago, David Heffernan said:

That trade off depends on the size of the record. For small records then performance is better if the items are stored directly in a contiguous array.

Sure. Anything depends on sizes, contents and use cases 🙂

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×