[fpc-devel] Allow object type property getter to expose a field as one of its base/parent types (patch included)

Kostas Michalopoulos badsectoracula at gmail.com
Sun Jun 14 03:00:41 CEST 2020


On Sat, Jun 13, 2020 at 12:07 PM Sven Barth via fpc-devel
<fpc-devel at lists.freepascal.org> wrote:
> I'm against it. It would essentially mean that you can't use the same
> field for the setter or that you'd need to go through a method.
> Exchanging one restriction (can't use a base class for the property) for
> another (can't use the same field for the setter) isn't worth it
> especially as you can avoid performance issues by using a getter method
> that is declared inline (the only case where this won't help is if you
> access it using RTTI, but one call more or less doesn't really change
> much anyway).

These two restrictions are not equal, using a setter method is much
more common than using a getter that accesses the field directly. Also
there is no exchange since you cannot use a property with a base type
as a setter for the base field. None of this functionality already
exists, this patch does not introduce any new restriction, it only
removes an existing one (which is even inconsistent since in other
places you can use values as their base type when the base type is
needed).

Performance-wise inlining doesn't always work (more often than not the
compiler fails to inline methods, especially when nesting inlines) and
so it cannot be relied upon. In addition, using a getter method does
change the RTTI data which is a significant difference.

To give you an idea on how i'm using this, i have a generic object
type (not class) that provides a read only view of a collection and a
mutable view that extends it, like this:

generic TDynArrayView<T> = object ...
generic TDynArray<T> = object(specialize TDynArrayView<T>) ...

This allows me to use a property like

property Items: specialize TDynArrayView<T> read FItems; // FItems is TDynArray

that always provides direct access to the items but without the API
allowing any modifications that ensures all modifications to the
collection itself will be done through the methods of the class that
provides the property but all accesses will have minimum overhead.

The RTTI data is also important because i have a custom serialization
system that tries to automate as much as possible with minimal
hand-holding (i also use custom attributes for this). All objects are
assumed to descent from a TRootObject object that provides functions
like SerializeToStream/DeserializeFromStream and this does deep
de/serialization (ie. serializes all objects, not just just the root
one).

This takes advantage of getter-only field-access properties to assume
that they are meant to also be serialized and owned by the object
itself (which is a safe assumption for most objects, custom attributes
can be used to change the behavior) so when deserializing, it writes
to those getter-only field-access properties directly. The implication
of this is that the address of the field must be available and thus an
getter function wouldn't work (before doing the patch i did try to
take the offset of the field variable and store it in a custom
attribute as a workaround, but it didn't work since parameters to
custom attributes do not seem to be able to do that - and besides i'm
not a fan of this since it adds extra noise to the declaration and
custom attributes seem to be unable to have any statically allocated
data and you need to allocate new instances just to access their data
- though that is another issue that i have with custom attributes in
general).

What the above means is that i can have two objects like

TSomething = class(TRootObject) ...

TAnother = class(TRootObject)
private
  FSomething: TSomething;
published
  property Something: FSomething read FSomething;
end;

and then when i do Another.SerializeToStream(SomeStream) and
Another.DeserializeFromStream(SomeStream) i can have the TSomething
instance automatically be stored and recreated without any additional
hand-holding boilerplate code (you need to provide a default Create
constructor, but this is basically the same requirement like
TPersistent). I also have a THidden custom attribute which is used to
mark published properties that are not to be exposed to the UI.

Also note that i use both of the above cases with classes like

generic TItemCollectionView<T> = class(TRootObject) ...
generic TItemCollection<T> = class(specialize TItemCollectionView<T>) ...

Which allows me to automatically de/serialize collections owned by
objects without any additional custom code (these two classes actually
wrap the TDynArray generic objects i mentioned above).

So basically as you see not only inline functions aren't equivalent
(they change the RTTI data, they can potentially be slower -
especially when considering value types like objects) but i'm actually
having two real use cases for this functionality.

I cannot think of a use case for the restriction you mention about
allowing to write to the setter (and again, that wouldn't make sense
logically either - you can "get" a base type value from a derived type
value, but nowhere in the language you can "put" a base type value to
a derived type value since that base type might be some other derived
type that doesn't match).


More information about the fpc-devel mailing list