📜 ⬆️ ⬇️

Dotting i in Delphi RAII

The topic of RAII in Delphi is usually silenced or information on this issue is limited to discussing the usefulness of interfaces. But interfaces one by one do not provide many of the desired features. When in Delphi 2006 there was an overload of operations, private fields of records, own constructors and methods in records, and it seemed it would be logical to see the automatically called destructor. Both run-time allows, and in the request section for new features of Delphi over the course of several years, query № 21729 “Record Operator Overloading: Please implement“ Initialize ”and“ Finalize ”operators” has been in TOP-10. Probably not destiny. Nothing, I will show how to do without failed features. Since Delphi 7 is very much alive, solutions compatible with Delphi 7 will be considered, including

Time to find workarounds was enough. This article is not a tutorial and is intended for advanced Delphi developers who write their own libraries or bind to libraries in other programming languages.

Why do you want RAII in Delphi?


')


For which types of Delphi does automatic control work?





Among all these features, only interfaces and options are programmable.

What are the bad interfaces?




What are the bad options?




How to solve most of these problems?



The solution I propose is to wrap interfaces or variants inside the private part of the record. We declare the type of record. We declare the type of interface. We duplicate all the methods in the interface and in the record. Recording methods redirect all calls to an internal object, and you can do what the interface type variable itself cannot do.

In the implementation of each write method, we consider the case when the private field is nil - it may be necessary to automatically initialize the object before invoking something from it. If you need to implement Copy-on-write, the method is declared in the interface.

procedure Unique(var Obj: IOurInterface); 


This method determines its uniqueness by the reference counter. If the object is not unique, the object must create its own copy and write this pointer in Obj instead of itself. Each write method that can change something must ensure that the pointer is unique before transferring control to the interface method. For internal needs, it is possible to provide var Obj: IOurInterface with other interface methods. For example, by analogy with built-in strings, there may be a desire to make it so that when there is no character left in a string of its own type, the dynamically allocated object is deleted, and the internal pointer becomes nil

In order to optimize, when implementing your own strings or long arithmetic, it may be necessary to consider the special case a: = a + b. I can not guarantee that it will work, but you can try when implementing the operation + compare the @ Self and @ Result pointers

The problem of unconditional automatic initialization of the internal field is fundamentally insoluble, but it can be initialized at the first call. The remaining problems are solvable either by wrapping the interface into a record, or by wrapping the option into a record, more on that later.

The record option is like a marshmallow in the glaze, but the record option



Own type of option gives more complete control in comparison with the interface. Since the variant field is private and outside this option should not leak, it is possible to implement only a minimal set of methods of your own (custom) type of option. Apart from a debugger trying to cast (CastTo) a variant to a string, when you hover the cursor, you will need to implement a copy (Copy) and destroy (Clear) variant. In operative memory, own types of a variant, as a rule, consist of a variant type marker and a pointer (for example, a TObject descendant). How this is done, I propose to look at the example of the implementation of complex numbers (VarCmplx.pas), which is present, at least since Delphi 7

Using variants would be useful for a single-stranded wrapper CFString. If you do a wrapper for the interfaces, Delphi will call AddRef and Release on the interface, but CFString is not an interface, and you will need to either wrap CFString into an additional layer of indirection from the interface, or use your own type of variant that causes CFRetain and CFRelease required for normal memory management. CFString. This would work much better than the CFString wrapper that Embarcadero offers in Delphi XE2.

Hey, what about Delphi 7?



Delphi is a language with a long history, and before the Delphi object system appeared, there was another object system in Borland Pascal with Objects. In Delphi 7 and Delphi 2005, it still functions. Instead of a record, the object keyword is written, and the resulting type is in many ways similar to the record in Delphi 2006: it may have private fields, it may have methods. Objects of the same type can be assigned to each other, in this sense they are also analogous to record. Just what we need. The compiler will swear on the unsafe type, there is no overload of operations, but this is the only inconvenience. The similarity of object and record is so great that it is possible, using conditional compiler directives, on older versions of Delphi to declare a type as object, and on new ones - as a record. That's exactly what I did in my small Delphi-CVariants collection library .

Problems can arise if one tries to declare several such types using each other. Cyclic dependencies in the source code are provided for classes, interfaces, and pointers, but not for objects as is. It is preferable to declare objects so that each next knows about the previous ones, but not vice versa. Therefore, for example, in my library, CMapIterator knows about CVariant, but CVariant does not know about CMapIterator.

Source: https://habr.com/ru/post/163241/


All Articles