#1 2010-08-06 09:45:50

ab
Administrator
From: France
Registered: 2010-06-21
Posts: 14,740
Website

Save object, stop class hegemony!

In a recent thread in the Embarcadero Discussion Forums, Vedran Vuk posted some questions about object keyword.
His purpose was to use object instead of class to improve speed and memory consumption:

Vedran wrote:

I do use classes. I just want to use objects on smaller classes that don't really need initialization or RTTI. Plus, I can directly operate on it like a record with no need for constructors and it can be sealed and has inheritance. Every byte matters in this case.

Rudy, on the EMB forum, wrote:

The "object" type is deprecated. As was said, it mainly exists for compatibility with old Turbo Pascal. That is why it is not documented very well. It's use is not promoted.

I have the same requirement sometimes, for example for our framework or for low-level units.
I do like such plain-old object type, and don't want to see this object feature marked as deprecated in future EMB versions.

Delphi has always had three kind of coders:
1. high-level coders, using components and RAD, standard library and its classes;
2. low-level coders, using pure code SW development, looking at the asm code generated and tracking down memory and CPU usage;
3. and to be honest, all low-level coders use the high-level tools, because for a simple form, RAD is great, and standard library is to be used anyway.

In fact, the object type is one feature which low-level coders like a lot.

For example, the whole KOL library relies and it, and performs a great job with it. The XMLRad framework uses it a lot for all basic types, in order to increase speed, and improve multi-threaded server response time.

Here are some cases I use object for:
- a memory mapped file, which I want to parse very quickly: a pointer to such an object is just great, and you still have methods at hand; I use this for TFileHeader or TFileInfo which map the .zip header, in SynZip.pas;
- a Win32 structure, as defined by a API call, in which I put handy methods;
- a temporary structure defined on the stack, just used during a procedure: I use this for TZStream in SynZip.pas, or for our RTTI related classes, which map the Delphi generated RTTI in an Object-Oriented way not as the TypeInfo which is function/procedure oriented. By mapping the RTTI memory content directly, our code is faster than using the new RTTI classes created on the heap. We don't instanciate any memory, which, for an ORM framework like ours, is good for its speed. We need a lot of RTTI info, but we need it quick, we need it directly.

In fact, I discovered that new Delphi compiler doesn't like packed object.
See http://qc.embarcadero.com/wc/qcmain.aspx?d=79792
I've an Internal Error DT5830 under Delphi 2009 Update 3, but previous compiler versions (including Delphi 2009 Update 2) did accept a packed object declaration.
There is a simple workaround: define it as packed record... this works, but is not compatible with previous version of the compiler.
So here is how must a packed object be coded:

type
{$A-} { force packed object (not allowed under Delphi 2009) }
  /// the internal memory structure as expected by the ZLib library
  TZStream = {$ifdef UNICODE}record{$else}object{$endif}
...

It's a pity that Embarcadero seems not to a have a full regression test of the compiler features, and run it before every release, like Free Pascal compiler does.
It's a pity that EMB changed the record type, by allowing adding methods on it. IMHO, it's exactly a no-feature. Noone asked for it, or for any good reason. If you want a "pure object pascal language", just use PRISM.

So here is my today's claim:

Don't forget low-level Delphi coders! Save our object type!

Offline

#2 2010-08-06 09:55:01

ab
Administrator
From: France
Registered: 2010-06-21
Posts: 14,740
Website

Re: Save object, stop class hegemony!

After having found out this QC entry:
http://qc.embarcadero.com/wc/qcmain.aspx?d=71723

See what was written in a stackoverflow thread:

http://stackoverflow.com/questions/1013489/problem-with-delphi-2009-and-old-style-object-type wrote:

I sent an e-mail to our local representatives from Embarcadero in regards to this problem and referred them to the report on Quality Central. They basically told us to move all objects to classes, so I'm guessing they're not planning on fixing this...ever. I think we've pretty much accepted that this is the way we have to go if we want to move forward, so now we just have to schedule that work before we can proceed with our upgrade to Delphi 2009.
Just wanted to thank everyone who tried to help, but I believe at this point it's a lost cause sad

I just feel not right. EMB, please help!

Offline

#3 2010-08-06 19:08:20

ab
Administrator
From: France
Registered: 2010-06-21
Posts: 14,740
Website

Re: Save object, stop class hegemony!

Wayne Niddery wrote:

While I can appreciate the problems caused when something like this breaks
existing code, and/or what you see as advantages to the old format, the fact
is everyone has had 15 years so far to get off the old object type. The
old type has been deprecated and actively discouraged since the intro of
Delphi. While Delphi has been amazingly backward compatible over the years,
including with this, how long is long enough?

To resurrect the old object type would mean three types of "objects"
actively supported and documented (not even counting interfaces).

While instantiating classes does have a bit of overhead comapred to the old
type, I have never found an case where it made any significant difference in
the performance of anything I've written, and even then there are ways to
improve that - pre-allocating, caching, pooling. etc. There is even a way to
allocate space for multiple class instances in one go if you really need
to try to squeeze out a few more cycles.

Depending on what you are doing, you could possibly have a singleton class
instance managing an array of records, putting an "OO" face on them.

IMHO, objects were rightly deprecated with the intro of Delphi and, given
that there are plans underway to introduce a new compiler before too long, I
personally hope they, once and for all, omit old-style objects from the new
implementation.

I guess you're enumerating: record, object and class. Right?
I don't speak about resurrection, Easter is far away from now.
I'm just saying: "don't kill object".

My problem is in fact a question:
Why did EMB decide to add method definition to record, then introduced the "packed object" bug in Delphi 2009 update 3, therefore forced me to write type definition like this if I want my code to compile from Delphi 7 to Delphi 2010:

type
{$A-} { force packed object (not allowed under Delphi 2009) }
  /// the internal memory structure as expected by the ZLib library
  TZStream = {$ifdef UNICODE}record{$else}object{$endif}
...

Record was also 15 years old (and even older).
Record is needed, because it maps some memory structures used by the OS, mapping plain C struct.
Record is needed, when you want to use pointers to already allocated memory, which is very usual if you need performance.
Record was modified in order to have methods, object was deprecated.

Just go to the OO roots:
A record is a memory structure.
An object is a memory structure with methods.

This new "record" type just doesn't make sense, and is confusing.
That was a no-feature. Just please marketing, add a new checkbox to the product feature matrix?
Who was complaining about the fact that records didn't have any method?
Who was complaining about the fact that you could use "object" instead of "class" then have a record with method, what is called an object?

I don't want to stick to Delphi 7 for ever. I bought other versions. But my previous employers did have a Delphi 7 based application, and I managed to make all i18n without upgrading to Delphi 2009/2010. Medical applications have some SQA and RA specifications.
And since I'm writing some open source units, I just think it's great to support users from Delphi 5 up to Delphi 2010.

So my opinion is that an object, i.e. a record with methods, has its rights to live forever in the unmanaged object pascal world.

Offline

#4 2010-08-07 09:00:03

ab
Administrator
From: France
Registered: 2010-06-21
Posts: 14,740
Website

Re: Save object, stop class hegemony!

Wayne Niddery wrote:

then introduced the "packed object" bug in Delphi 2009 update 3,
A bug is a bug, it wasn't done deliberately.

Of course, there is no conspiracy here.
But what is deliberate is not fixing it.
This new "record" type just doesn't make sense, and is confusing.
Why? What's confusing about it?

In the classic OO world:
record = memory structure
object = memory structure with a method

In the EMB OO world:
record = memory structure with a method
object = something evil, deprecated, impure

Wayne Niddery wrote:

You're entitled to your opinion of course, but there is essentially no
chance they will un-deprecate the object type any time soon. If you need
stack-based objects, use records; if you need virtual, polymorphic objects,
use classes. Essentially everything that can be done with the old object
type is covered by these. I really cannot see anything I could do with the
old object type that I cannot do just as well now with either classes or
records (and I have used Pascal objects since they were introduced in TP5.5,
not just since Delphi).

That's exactly my point.
The new type of record is exactly what I need for struct mapping or fast code accessing to already allocated memory.
But code doesn't compile with older version of Delphi. Which is annoying, if, like me, you care about giving source code to most Delphi versions.

To sum up:
- we all agree about the fact that Delphi classes are great for Delphi programming - I don't want to instanciate object with virtual methods;
- I just regret the fact that record type was modified - object is an object, record is a record - back to the OO basics, and code is difficult to write with backward compatibility (it's not just a new type definition, like string which can be overridden by using explicitly AnsiString and AnsiChar e.g., it's a keyword change, that we can't override nicely: a conditional define is necessary, which is not elegant);
- I'm confident, as Rudy just wrote here, that "I dobut it will be killed anytime soon";
- but I'm still a bit afraid that, as Rudy also wrote, "the promised new front end might finally get rid of it"...

Offline

#5 2010-08-07 09:01:39

ab
Administrator
From: France
Registered: 2010-06-21
Posts: 14,740
Website

Re: Save object, stop class hegemony!

Alexandru Ciobanu wrote:

OO is a concept that is constantly evolving. I don't want Delphi to
remain an old language. I want new features that saves me a lot of
coding and makes code more logical and readable.

Go to the roots:

http://en.wikipedia.org/wiki/Object-oriented_programming wrote:

Object-oriented programming (OOP) is a programming paradigm that uses "objects" – data structures consisting of data fields and methods together with their interactions – to design applications and computer programs.

So calling a record with methods like an object makes perfectly sense.
And don't speak about DotNet or Java: in these, all is object, even an integer number (yes, I know that the virtual machine makes optimization and doesn't instanciate a whole object for just an int). From their syntax, all is object, even struct.

Alexandru Ciobanu wrote:

"object", "class" ... it's getting confusing. I think it's time that object get a proper funeral.

It was sometimes very useful to me to use object types with inheritance, which can't be done with record.
My point was to map some already allocated data (in a memory mapped file, or a uncompressed buffer), then use pointer of such object to get data and methods on this memory.
I don't need virtual methods and such, just object with diverse methods inside, depending on the data mapped. And the fact to inherit from one root object class to another.
Resulting code is very easy to read and maintain, thanks to the OO approach of methods, and the code was extremely fast, because it's using just pointers.
You can do that with variable records (a case in a record), then functions, but it's awful, unreadable, and difficult to maintain.
I admit this was a very specific task, but why get rid of this object feature, if it could be handy.

And how is confused about the object keyword? Aren't Delphi beginners confused about threadvar, variable records, asm or absolute keyword? Should we get rid of these?

Alexandru Ciobanu wrote:

What is missing, IMO is
1. Allow a record to inherit from another record. (no virtuals
obviously; allow reintroducing of methods and operators).
2. Allow a class to inherit from another record.
This would pretty much resolve all issues associated with records ATM.

I'm okay.
And.... that's exactly what object do.
So, I'm OK with deprecating constructors and virtual methods on object.
But I'm not OK with deprecating the whole object concept (i.e. a record with methods and inheritance) just because we'll have to map with C# struct. See http://en.csharp-online.net/struct

Delphi is in an unmanaged world, and pointers are not evil. Pointers of object, having both methods and inheritance, have their meaning and interest.

I'm very pleased arguing with you, because we use Delphi in two diverse manners.
I like asm and code efficiency. The more I use Delphi, the more I feel like home in the Linux kernel or SQLite3 sources, if you follow what I mean. I wrote some speed and low-resource dedicated framework (Synopse SQLite3) or units (like SynPdf). I enjoy the 64 bit and cross-CPU cross-OS features of FPC.
You like elegant syntax, and language features. You look after link and C#, and wrote a great general purpose library, DeHL. I guess you enjoy ruby on rails or PRISM.

This discussion just illustrates that: I spoke about generated asm code, pointers, algorithm and memory structures. You answer with language consistency, and the beauty of the new record features.

So we don't have to argue. Because we use the same tool with two diverse sensibilities or interests, and - perhaps - in two diverse directions.
On the same IDE. Delphi is great!

Offline

#6 2010-08-08 08:42:04

ab
Administrator
From: France
Registered: 2010-06-21
Posts: 14,740
Website

Re: Save object, stop class hegemony!

Rudy Velthuis (TeamB) wrote:

Records can do most of what the old object types could
do, they just don't know inheritance or virtual methods. If you need
those, you can use classes.

I already answered that a lot of times.
I posted some code about parsing a memory buffer using pointer of objects. Inherit object types made the code clear and easy to read, write, and split into multiple units. I had methods. That was my point.

Rudy Velthuis (TeamB) wrote:
AB wrote:

So who do you code thinks like I've coded above, i.e. accessing in an OO manner some already allocated data with pointers?

I don't know what you mean.

I posted here some code with pointer of object to parse a memory buffer. That was what I meant.

Rudy Velthuis (TeamB) wrote:

If they are used as reference types, it is much better to use classes, which also have virtual methods and a lot of other goodies.

We all agree for that, since the beginning.

Rudy Velthuis (TeamB) wrote:
AB wrote:

2. Rudy just answered here: "use record helpers"

No, I didn't. I never told anyone to use record helpers. I only said why you can't add fields in a helper.

I asked you how to replace object. You spoke about using records and "delegation".
I asked you if it meant using record helper. Then asked some details about their implementation.
So what do you call "delegation" in your mind, if it was not by using record helper?

Rudy Velthuis (TeamB) wrote:

I personally think that one should avoid any kind of "helpers". Just retype the "base record" fields in your descendant class, or add an
instance of the "base record" as first field.

You're right, that's exactly what I'll have to to. My TObject typecast is a bit tricky.
But use of pointer of object made my code easy and clear.

Rudy Velthuis (TeamB) wrote:

IMO, it is NOT a good idea to make TMyRect INHERIT from TMyPoint. A
TRect should CONTAIN two TPoints or a TPoint and a TSize.

Of course, the point of my sample code was NOT the TRect/TPoint mapping, but show how to bytecast and access record content from TObject types, with some kind of inheritance.

Rudy Velthuis (TeamB) wrote:

That's horrible! I have no idea what you are actually trying to do, but ISTM that variant records could be the solution. Or simply different kinds of pointers.

I already wrote that variant records are also horrible, and can't be split into multiple units.
Different kind of pointers, or record with parent fields as first property will to the work.

But object was perfect for my purpose.

What is incredible in this discussion, is that I posted some code of what I wanted to do, proposed two other implementations.... and the only code which was commented was the last one, which was a consequence of the removing of object.
It's like no one read the first code, i.e. the one using pointer of object.

AB wrote:
type
  PMyObject = ^TMyObject;
  TMyObject = object
    KindOf: integer;
  end;
  PMyShortString = ^TMyShortString;
  TMyShortString = object(TMyObject) // with KindOf=1
    Data: ShortString;
    function Next: PMyObject; inline;
  end;
  PMyInteger = ^TMyInteger;
  TMyInteger = object(TMyObject) // with KindOf=2
    Data: integer;
    function Next: PMyObject; inline;
  end;

procedure ParseBuffer(Buf: PMyObject);
begin
  while Buf^.KindOf<>0 do begin
    case Buf^.KindOf of
    1: with PMyShortString(Buf) ^ do begin
      writeln(Data);
      Buf := Next;
    end;
    2: with PMyInteger(Buf) ^ do begin
      writeln(Data);
      Buf := Next;
    end;
   end;
  end;

And to be clear, variant records are not a solution:
- I need sometime to have a declaration in another unit;
- can methods follow variant records?
- what was great with object is that you can inherit then create some methods with the same name (for example Next in my first sample code), making the code much clearer.

Putting parent fields as first property of record will do the trick.
Another possibility could be to use C-like "objects", i.e. putting the pointer as first argument of a function. But it's not as powerful and clear as pointer of object.
But it's definitively a pity to implement these like that.
A real regression of the language, by comparison to the object possibility.

I like the new syntax features, but don't forget some Delphi user still code using pointers and generated asm in mind. Or perhaps I have no place in the Delphi galaxy, and have to move to C or FPC?
I'm working on an open source framework, named XMLRAD, which is used worldwide by hundreds of thousands users. Since it's web server based, the more user we put on a HW server, the better for finance. Here we use Delphi and pointers. Avoiding string and FastMM4 usage (which are realy evil on multi-core CPU because of locking), using dedicated classes and objects. In this application, there is no component, only source code.
Not every Delphi user plays with the RAD. Some like just coding. So deprecating a so interesting feature like object, without giving any other way to do it..it's a pity.

Perhaps if record could inherit, from another record, it could solve my issue here.
I doubt EMB will implement it, for the reasons (minus #2 of Rudy authority) I wrote above.

AB wrote:

1. The C# equivalence is struct, which doesn't allow inheritance, and its sounds like C# is the new canonical reference for language features;
2. Rudy just answered here: "use record helpers" - and I'm quite sure he knows about EMB orientation;
3. They won't allow two structures (class+record) with inheritance in the future Delphi language;
4. You can use a normal class, with static methods, to map some memory just as records (I just realized that, when I was told that object are deprecated and must not to be used any more... I had to find a solution, and here it is);
5. Memory allocation of the class instances is not very time consuming with the new heap manager (even if FastMM4 is not multi-core friendly, but that's another problem);
6. It's not a very marketing-appetizing feature.
7. whatever

Offline

#7 2010-08-08 11:09:11

ab
Administrator
From: France
Registered: 2010-06-21
Posts: 14,740
Website

Re: Save object, stop class hegemony!

Alexandru Ciobanu wrote:
TComposedBlock = packed record
private
FDataBlock1: TDataBlock1;
FMoreStruff: ....
public
property DataBlock: TDataBlock1 read FDataBlock1;
end;

You don't need inheritance here. This is actually the way I use it when I need composed data structures. Of course depending on the functionality you might want to make some fields public without properties and etc.

The fact to encapsulate the first TDataBlock in the TComposedBlock works as expected, but it's less elegant than the plain object type, or a future record with inheritance.
That was my point all along this forum thread

Alexandru Ciobanu wrote:

I've written OOP code with ASM functions in the past, I don't see how API "beauty" excludes performance.

I know that, but sometimes compiler tricks make generated code slower: implicit try..finally block, locked reference counting in string, dynamic arrays or interfaces, locked memory for FastMM4, data copy into temporary hidden var (e.g. see any string concatenation, or why variants may be slow), runtime code checking for the right type (e.g. sometimes implicit call of multibytetowidechar API when using AnsiString and UnicodeString), new RTTI usage needing the creation of class instances to get the info, and so on...

"brut" types like record, object and champagne make me fell just at home with the code generated by the Delphi compiler.
Of course, I don't want to abuse. But when profiling of very stressed server applications (like XMLRad), such tricks are needed.

Alexandru Ciobanu wrote:

DeHL is not about copying C# or Java or etc. It's about trying to bring some useful methodologies to Delphi users in a Delphi way.

Indeed, you've done great stuff here.

Alexandru Ciobanu wrote:

The discussion started with object vs record  It has nothing to do with ASM, pointers and algorithms. I thought we were talking about why is object killed and why advanced records are useful.

But by killing object, my object pointers were killed too.
As a collateral effect.
Therefore, "advanced records" with inheritance just sounded like reinventing the wheel for me.

Alexandru Ciobanu wrote:

No, but it's fun to argue. What else can you do an a sunny day with nearly 40C ...

I'm far from the beach. But heah! I'm in a little french village, at 1000 meters height, named Cauterets. It's Sunday, so I'll make some walk, then go to the Mass on the evening... but always coding in my head. I'm sure you know that feeling too (I mean not walking in mountains, but having code and algorithms running in your head all day long).

I'm perhaps going in the wrong way, but the more I use Delphi, the more my brain becomes a compiler.

When I write Delphi code, I know by experience which asm code will be generated, how memory will be used, which RTL calls will be made, so... I type Delphi words, but I'm thinking how the CPU will execute it, how its internal cache, pipelined or multi-core mechanisms will handle it. It's just the biggest fun in coding, for me.
That what I meant by speaking about Linux or SQLite3 source. Both have the same approach in coding. That's the beauty of C, and object pascal has it, with a much more delicate syntax (like strong typing), and built-in object orientation.

Sometimes I code like I could script it in Python and Ruby (like our SynProject tool, which use a lot of tupples-like classes).
But what I really enjoy is Delphi coding like if it was a big assembler macro framework, with full object-oriented code clarity and high-level memory structures. And I'm not forgetting about algorithm, that's exactly the contrary.

Offline

#8 2010-11-24 06:56:22

ab
Administrator
From: France
Registered: 2010-06-21
Posts: 14,740
Website

Re: Save object, stop class hegemony!

Just an example of backward compatibility, when object is to be used instead of record for a library to be compiled with Delphi 6/7/2007:
  See http://synopse.info/forum/viewtopic.php?pid=777#p777

Offline

#9 2011-09-29 05:33:29

ab
Administrator
From: France
Registered: 2010-06-21
Posts: 14,740
Website

Re: Save object, stop class hegemony!

Perhaps this issue sounds like the one fixed with XE2 update 1.
See QC #98448 at http://dn.embarcadero.com/article/41649

At least!

Offline

#10 2012-04-04 09:35:52

Arioch
Member
Registered: 2011-11-17
Posts: 28

Re: Save object, stop class hegemony!

class constructors are cool

AFAIR they can be applied to records, but not to objects :-)

Offline

#11 2012-04-04 09:42:15

Arioch
Member
Registered: 2011-11-17
Posts: 28

Re: Save object, stop class hegemony!

ab wrote:

In the classic OO world:
record = memory structure
object = memory structure with a method

AFAIR that was not the case in C++

Offline

#12 2012-04-04 11:59:34

ab
Administrator
From: France
Registered: 2010-06-21
Posts: 14,740
Website

Re: Save object, stop class hegemony!

AFAIK C++ is not "classic"... it is "classic ++", i.e. its own world...
smile

Offline

#13 2023-09-15 08:26:27

ab
Administrator
From: France
Registered: 2010-06-21
Posts: 14,740
Website

Re: Save object, stop class hegemony!

Back to an old thread.

With mORMot 2, I had to ensure no "object" is used anywhere in the source code, because the Delphi compiler seems to fail compiling it... only when compiling into packages...
I had to make a HUGE refactoring https://synopse.info/forum/viewtopic.ph … 182#p40182

Now, the mORMot code is filled with plenty of

  {$ifdef USERECORDWITHMETHODS}
  TSortedWordArray = record
  {$else}
  TSortedWordArray = object
  {$endif USERECORDWITHMETHODS}

And the worse is when we need inheritance between units - a clear showcase of what we were discussed in this thread:

  {$ifdef USERECORDWITHMETHODS}
  TJsonParserContext = record
  public
    Get: TGetJsonField;
    function Json: PUtf8Char;       {$ifdef HASINLINE} inline; {$endif}
    function Value: PUtf8Char;      {$ifdef HASINLINE} inline; {$endif}
    function ValueLen: PtrInt;      {$ifdef HASINLINE} inline; {$endif}
    function WasString: boolean;    {$ifdef HASINLINE} inline; {$endif}
    function EndOfObject: AnsiChar; {$ifdef HASINLINE} inline; {$endif}
  {$else}
  TJsonParserContext = object(TGetJsonField)
  {$endif USERECORDWITHMETHODS}

This is IMHO a clear proof that the Delphi compiler just broke backward compatibility, and deprecating such features was just a mistake.
Of course, we could just get rid of Delphi 7 and 2007 compatibility, but a non-negligible set of users are still maintaining such pre-Unicode projects, and mORMot is a good help for them.

We tried to avoid as many IFDEF as possible in the new mORMot 2 codebase, but Delphi bad marketing choices just made it impossible.

Offline

#14 2024-05-08 08:30:30

malom
Member
Registered: 2017-01-12
Posts: 4

Re: Save object, stop class hegemony!

Somewhat related to this topic, and confirms, that old style TP "object" could be very useful, and should not be deprecated. Some time ago I found, that "object"s do support interfaces, at least at syntax level. I posted it as a comment, but I think, only Mr. Pluimers have read it:
https://wiert.me/2014/02/20/hidden-feat … these-too/
Look at the declarations here. The implementation is in the blog comment...That is poor and not very interesting.

type

  TInterfacedBase = object // Not CLASS
  private
    FInterfaceTable: Pointer;
    FRefCount: Integer;
  public
    constructor Init;
    destructor Done;
    function GetInterface: IInterface;
    function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall;
    function _AddRef: Integer; stdcall;
    function _Release: Integer; stdcall;
  end;

  ITest = interface
    function Foo: string;
  end;

  // TTest = object(ITest) // Must have an ancestor OBJECT type or gives a syntax error
  TTest = object(TInterfacedBase, ITest)
  public
    constructor Init;
    function Foo: string;
  end;

Offline

Board footer

Powered by FluxBB