Is there a benefit in using old style `object` instead of `class` in Delphi?

前端 未结 4 1531
刺人心
刺人心 2020-12-31 15:31

In Delphi sane people use a class to define objects.
In Turbo Pascal for Windows we used object and today you can still use

相关标签:
4条回答
  • 2020-12-31 16:04

    Object was not the Delphi 1 method of setting up objects; it was the short-lived Turbo Pascal method of setting up objects, which was replaced by the Delphi TObject model in Delphi 1. It was kept around for backwards compatibility, but it should be avoided for a few reasons:

    1. As you noted, it's broken in more recent versions. And AFAIK there are no plans to fix it.
    2. It's a conceptualy wrong object model. The entire point of Object Oriented Programming, the one thing that really distinguishes it from procedural programming, is Liskov substitution (inheritance and polymorphism), and inheritance and value types don't mix.
    3. You lose support for a lot of features that require TObject descendants.
    4. If you really need value types that don't need to be dynamically allocated and initialized, you can use records instead. You can't inherit from them, but you can't do that very well with object either so you're not losing anything here.

    As for the rest of the question, there aren't all that many speed benefits. The TObject model is plenty fast, especially if you're using the FastMM memory manager to speed up creation and destruction of objects, and if your objects contain lots of fields they can even be faster than records in a lot of cases, because they're passed by reference and don't have to be copied around for each function call.

    0 讨论(0)
  • 2020-12-31 16:10

    When given a choice between "fast and possibly broken" and "fast and correct," always choose the latter.

    Old-style objects offer no speed incentive over plain old records, so wherever you might be tempted to use old-style objects, you can use records instead without the risk of having uninitialized compiler-managed types or broken virtual methods. If your version of Delphi doesn't support records with methods, then just use standalone procedures instead.

    0 讨论(0)
  • 2020-12-31 16:12

    For using normal OOP programming, you should always use the class kind. You'll have the most powerful object model in Delphi, including interface and generics (in later Delphi versions).

    1. Records, pointers and objects

    Records can be evil (slow hidden copy if you forgot to declare a parameter as const, record hidden slow cleanup code, a fillchar would make any string in record become a memory leak...), but they are sometimes very convenient to access a binary structure (e.g. some "smallish value"), via a pointer.

    A dynamic array of tiny records (e.g. with one integer and one double field) will be much faster than a TList of small classes; with our TDynArray wrapper, you will have high-level access to the records, with serialization, sorting, hashing and such.

    If using pointers, you must know what you are doing. It's definitively more preferable to stick with classes, and TPersistent if you want to use the magical "VCL component ownership model".

    Inheritance is not allowed for records. You'll need either to use a "variant record" (using the case keyword in its type definition), either use nested records. When using C-like API, you'll sometimes have to use object-oriented structures. Using nested records or variant records is IMHO much less clear than the good old "object" inheritance model.

    2. When to use object

    But there are some places where objects are a good way of accessing already existing data.

    Even the object model is better than the new record model, because it handles simple inheritance.

    In a Blog entry last summer, I posted some possibilities to still use objects:

    • 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 for easy access to the data (for that you may use record but if there is some object orientation in the struct - which is very common - you'll have to nest records, which is not the very handy);

    • 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.

    3. How object implementation is broken in modern Delphi

    The fact that object is broken in modern Delphi is a shame, IMHO.

    Normally, if you define a record on the stack, containing some reference-counted variables (like a string), it will be initialized by some compiler magic code, at the begin level of the method/function:

    type TObj = object Int: integer; Str: string; end;
    procedure Test;
    var O: TObj
    begin // here, an _InitializeRecord(@O,TypeInfo(TObj)) call is made
      O.Str := 'test';
      (...)
    end;  // here, a _FinalizeRecord(@O,TypeInfo(TObj)) call is made
    

    Those _InitializeRecord and _FinalizeRecord will "prepare" then "release" the O.Str variable.

    With Delphi 2010, I found out that sometimes, this _InitializeRecord() was not always made. If the record has only some no public fields, the hidden calls are sometimes not generated by the compiler.

    Just build the source again, and there will be...

    The only solution I found out was using the record keyword instead of object.

    So here is how the resulting code looks like:

    /// used to store and retrieve Words in a sorted array
    // - is defined either as an object either as a record, due to a bug
    // in Delphi 2010 compiler (at least): this structure is not initialized
    // if defined as a record on the stack, but will be as an object
    TSortedWordArray = {$ifdef UNICODE}record{$else}object{$endif}
    public
      Values: TWordDynArray;
      Count: integer;
      /// add a value into the sorted array
      // - return the index of the new inserted value into the Values[] array
      // - return -(foundindex+1) if this value is already in the Values[] array
      function Add(aValue: Word): PtrInt;
      /// return the index if the supplied value in the Values[] array
      // - return -1 if not found
      function IndexOf(aValue: Word): PtrInt; {$ifdef HASINLINE}inline;{$endif}
    end;
    

    The {$ifdef UNICODE}record{$else}object{$endif} is awful... but the code generation error didn't occur since..

    The resulting modifications in the source code are not huge, but a bit disappointing. I found out that older version of the IDE (e.g. Delphi 6/7) are not able to parse such declaration, so the class hierarchy will be broken in the editor... :(

    Backward compatibility should include regression tests. A lot of Delphi users stay to this product because of the existing code. Breaking features are very problematic for the Delphi future, IMHO: if you have to rewrite a lot of code, which shouldn't you just switch the project to C# or Java?

    0 讨论(0)
  • 2020-12-31 16:20

    Way back in older versions of Delphi which did not support records with methods then using object was the way to get your objects allocated on the stack. Very occasionally that would yield worthwhile performance benefits. Nowadays record is better. The only feature missing from record is the ability to inherit from another record.

    You give up a lot when you change from class to record so only consider it if the performance benefits are overwhelming.

    0 讨论(0)
提交回复
热议问题