Good old object is not to be deprecated - it is the future
Yes, I know this article title is a huge moment of trolling for most Delphi
object could be legend... - wait for it - ... dary!
You perhaps already noticed by several blog posts
here that I still like the good old (and deprecated)
type, in addition to the common heap-allocated
record with methods does not match the object-oriented
object, since it does not feature inheritance.
When you take a look at modern strongly-typed languages, targeting concurrent programming (you know, multi-thread/multi-core execution), you will see that the objects may be allocated in several ways, to facilitate execution flow.
The Rust language for instance is pretty interesting. It has optional task-local Garbage Collection and safe pointer types with region analysis.
To some extent, it is very similar to what
object allows in the
Delphi world, and why I'm still using/loving it!
You have indeed several memory models around:
- Manual memory handling
C and C++ provide very fine-grained control over memory allocation; heap memory can be explicitly allocated and de-allocated.
- Full garbage collection
- Garbage collection with value types and references
C# for instance is essentially garbage-collected, but features value types which are guaranteed to be stack-allocated if in local variables.
- Manual memory handling with reference-counted types
This is where Delphi shines with its Copy-On-Write (COW) types and reference-counted
interface, and also Objective C with its ARC model (which is an more sophisticated approach to reference counting).
- Safe manual memory management
Rust falls into this category: you can choose where your object will be allocated (heap or stack), and how tasks work with it (it minimizes sharing by default, in contrast to other models).
But we will see that Delphi is not far away from it.
Rust memory model
Rust allocates objects in a task-oriented manner (extracted from their language definition):
Rust has a memory model centered around concurrently-executing tasks.
Thus its memory model and its concurrency model are best discussed simultaneously, as parts of each only make sense when considered from the perspective of the other.
When reading about the memory model, keep in mind that it is partitioned in order to support tasks; and when reading about tasks, keep in mind that their isolation and communication mechanisms are only possible due to the ownership and lifetime semantics of the memory model.
So the memory model is the following:
A Rust program's memory consists of a static set of items, a set of tasks each with its own stack, and a heap.
Immutable portions of the heap may be shared between tasks, mutable portions may not.
The immutability of memory portions does make sense, but I tend to like the
Delphi COW approach very much.
For instance, local variables in Rust are immutable unless declared with
Rust features a task-oriented memory model, which purpose is to avoid full
Garbage-Collection memory model.
The basic idea is to remove garbage collection from the core language and relegate it to the standard library, with a minimal set of language hooks in place to allow for flexible, pluggable automatic memory management.
As a consequence, you can allocate your objects from the stack or the heap - this is a difficult concept to grasp for many programmers used to languages like Java or C# that don’t make such a distinction.
In Rust, a box is a reference to a heap allocation holding another value.
There are two kinds of boxes: managed boxes and owned boxes.
- A managed box type or value is constructed by the prefix
- An owned box type or value is constructed by the prefix
is just the Rust equivalent of the C malloc and free functions;
@is a replacement for manual reference counting in C programs (i.e. when your value need to be managed outside the immediate execution scope);
&will define a "borrowed pointer", i.e. a reference to the object.
It results in a much better performance scalability, when used in concurrent mode.
For a more complete introduction to Rust's memory model, see this great article.
On Delphi side
object instead of
class - but
object in conjunction with
class/interface for most
There are a lot of opportunities when it does make sense to have:
- Individual objects allocated locally on stack within a method/function scope;
- Set of objects allocated at once on a [dynamic] array (local or shared);
- Map some binary content with strongly-typed pointers and
record, but object
allowinheritance, which is IMHO mandatory to follow proper OOP design - the Single Responsibility principle, for instance.
In Domain-Driven Design,
objectsare great to define value objects.
In my opinion, the Delphi language, in its current state, features a large
Reducing the Delphi language to just one string type or just one memory model (like with the Delphi NextGen compiler) is not a good idea.
It sounds like a lack of vision to me.
Some years ago, we were told by EMB that Garbage Collection was the
Now - thanks to the Apple hipe - the ARC model is the new model to follow.
When you take a wide look, e.g. when you look at Rust, you can see that a less monolithic approach (like the one existing on OldGen Delphi) could make sense!
From my understanding, the object pascal memory model (with dedicated
object types) may be more easy to work with
than the Rust
@ ~ obfuscated syntax.
And you are not stuck by hard coded principles like default immutability of tasks: in Delphi, you can still use a global heap, and rely on dedicated structures when you want better performance.
This is what we tried to do with mORMot: high-level methods are easy to work with, but its internal core uses low-level tricks (like pointer arithmetic or stack-allocated structures) to ensure the best possible scalability.
In a practical point of view, Object Pascal's Manual memory
handling with reference-counted types does still make sense.
In addition to the initial COW paradigm, the RCU (Read-Copy-Update) pattern could make sense: when used in multi-thread context, it allows lock-free shared access to resources (which is not allowed by Rust).
The proposal of a
threadlocalvar compiler enhancement
this very same blog, back in 2010 - still does make sense, and perfectly
fit with the memory model proposed by Rust.
A custom mode of
class allocation could make sense also, by
overriding the low-level allocation/release model of
allocate on the heap by default, but on the stack (with auto-release at the end
of scope) with an optional pattern.