I'm primarily a C++ programmer, and I've grown used to having class templates like std::unique_ptr
, std::shared_ptr
, etc for expressing ownership of my objects. Does Delphi have anything that is similar in its standard library? Are there any best-practices out there for expressing object ownership that I should be following as I write my code?
Edit: Since C++11 became standard, there are two lightweight helper classes, std::shared_ptr
and std::unique_ptr
.
If I create a variable of type std::shared_ptr<int>
, it represents a pointer to an int with shared ownership: under the hood is reference-counted, and when the ref-count reaches zero then the pointer is automatically freed. This type expresses a kind of "shared ownership", where many objects share the responsibility of destroying the resource when they are done with it.
In contrast, std::unique_ptr
expresses single ownership. When the unique_ptr goes out of scope, the resource is automatically freed. std::unique_ptr cannot be copied: there can be exactly one object owning this resource at a time, and there is exactly one object who is responsible to clean the object up.
Contrast these lightweight classes with a naked pointer to int, where it can represent either shared ownership, unique ownership, or it can just be a reference to an object somewhere else! The type tells you nothing.
My question is: as Delphi supports holding references to objects, are there any mechanisms for explicitly stating "I am the sole owner of this object, when I'm done with it, I will free it", vs "I am merely keeping a reference to this object around for the purpose of interacting with it, but somebody else will clean it up" vs "I share this object with many other objects, and whoever has it last gets to clean it up."
I know that Collections.Generics has different collections such as TList
vs TObjectList
, where TObjectList will free the members stored within it, but TList won't. You can say that TObjectList "owns" it's elements, whereas TList doesn't. This is the essence of my question, really. When designing my own classes, are there ways of directly expressing these kinds of ownership issues within the language? Or are there any best practices/naming conventions that are common amongst developers?
I am not aware of any language constructs that can help, nor of any "standard naming conventions".
However, long ago, I have adopted the following naming convention to make it easier to check whether classes clean up behind themselves properly:
Very crude, but it works, and has helped a lot when going through code you haven't seen in a while to prevent those "Wait, shouldn't that reference be freed or nilled?" searches.
std::unique_ptr cannot be copied: there can be exactly one object owning this resource at a time
In the Delphi language, there is no type nor mechanism that prevents to share 'ownership'. A copy of any reference can always be made. (Read: there's nothing in Delphi that allows you to block assignment, as David nicely put it.)
When the unique_ptr goes out of scope, the resource is automatically freed.
In Delphi, this is only possible with (or via) interfaces. Delphi has no garbage collector.
And there is exactly one object who is responsible to clean the object up.
Responsibility for cleaning up you have to enforce by yourself. Or delegate that task to a(nother) framework. For example, the default Delphi VCL class TComponent
implements automatic ownership (and destruction) that can optionally be exchanged/controlled with RemoveComponent
and InsertComponent
.
When designing my own classes, are there ways of directly expressing these kinds of ownership issues within the language? Or are there any best practices/naming conventions that are common amongst developers?
Not exactly on topic, but certainly related: there are multiple 'singleton' design pattern implementations that enforce single-time creation of objects.
Regarding naming conventions: the term "Owner" (or "OwnsObjects" from your own example) definitely expresses ownership in the sense that that owner will take care of destruction when necessary. Thus a button created with a form as owner (the single parameter of the button's default constructor) needs no manual destruction.
The concepts in Delphi differ from C++ in many occasions. Both languages are third generation, but Delphi likes to work on a higher level of abstraction than C++. For instance, Delphi supports pointers but they are rarely used when campared to the concept of reference, which is not precisely the same one as in C++.
In Delphi, object variables are in fact references (or in a lower level of abstraction, they are pointers). In C++, when you declare an object variable, the constructor is invokated imediatly, in Delphi it´s not and you have to call it in a given moment, what will allocate memory and run the constructor. So, the memory management of objects in C++ and Delphi are conditionated to different life cycles.
All this was said just to tell you that the memory management design style in Delphi is different than C++. That´s why Delphi doesn´t have any helper class that does precisely what you want. However, Delphi provides a concept named Interfaces
, that do not exist in C++ (at least, it didn´t when I used to work with C++, ages ago). Interfaces are similar to an abstract class in the sense they do not have code. You have to provide a class implementor to a interface and that class will provide the code. However, Interfaces
provide a reference-count memory management that, I believe, is close to what your are looking for.
So, my answer to you is: the closest language construct that Delphi has to offer you in terms of memory management that can be used to your purposes is Interfaces. So, I suggest that you study it at least a bit to get your own conclusions.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With