How to provide a derived instance with an ancestor field declaration? - delphi

I am having some trouble figuring out this case of inheritance.
In my class TBalans, I have a routine Initialiseer that takes a TBalPar object as parameter. TBalPar is the ancestor class of TNewBalPar that has additional fields. Now I would like to reach the additional fields from within my TBalans class. I still can feed a TNewBalPar object to the Initialiseer routine, but how do I get to the data of the descendant class?
What I tried is the following: I derived TBalans too into TNieuweBalans, gave it the new additional fields, and assign them in the routine:
type
TBalPar = class
//some vars
end;
TNewBalPar = class(TBalPar)
ExtraVar: TValue;
end;
TBalans = class
MyBalPar: TBalPar;
function Initialiseer(ABalPar: TBalPar): Boolean; virtual;
end;
TNieuweBalans = class(TBalans)
MyBalPar: TNewBalpar; //declared again so I don't need to cast it when using it
MyExtraVar: TValue;
function Initialiseer(ABalPar: TBalPar): Boolean; override;
end;
function TBalans.Initialiseer(ABalPar: TBalPar): Boolean;
begin
MyBalPar := ABalPar;
end;
function TNieuweBalans.Initialiseer(ABalPar: TBalPar): Boolean;
begin
inherited;
MyBalPar := TNewBalPar(ABalPar);
MyExtraVar := MyBalPar.ExtraVar; //instead of casting TNewBalPar(MyBalPar).ExtraVar
end;
This code works, but it feels wrong: I declare the MyBalPar field twice. I would like to improve on it.
Note that I am not looking for a way how to expose ExtraVar to the outside world, but how to use it conveniently within TNieuweBalans.
How can I eliminate the double MyBalPar field but still prevent frequent typecasting?

Current design
The need for a convenient designated derived field type for an ancestral field is not forbidden, nor uncommon for that matter. But your implementation, like you sense already, has some problems:
the doubled fields require unnecessary memory,
you need to synchronize changes to TBalans.MyBalPar and TNieuweBalans.MyBalPar,
you need to synchronize changes to TNieuweBalans.MyBalPar.ExtraVar and TNieuweBalans.MyExtraVar,
you do not enforce the derived class type: feeding a TBalPar object to TNieuweBalans.Initialiseer results in an access violation because MyBalPar.ExtraVar does not exist.
There are multiple ways to overcome each of these problems.
The most elementary solution to prevent extra fields is to provide properties for them with getters that extract the values from the inherited class (I renamed some of your types and variables for comprehensibility):
type
TBalPar = class(TObject)
// some variables
end;
TBalParEx = class(TBalPar)
private
FExtra: TValue;
public
property Extra: TValue read FExtra write FExtra;
end;
TBalance = class(TObject)
private
FBalPar: TBalPar;
public
function Initialize(ABalPar: TBalPar): Boolean; virtual;
property BalPar: TBalPar read FBalPar;
end;
TBalanceEx = class(TBalance)
private
function GetExtra: TValue;
procedure SetExtra(Value: TValue);
public
function BalPar: TBalParEx;
function Initialize(ABalPar: TBalPar): Boolean; override;
property Extra: TValue read GetExtra write SetExtra;
end;
function TBalanceEx.BalPar: TBalParEx;
begin
Result := TBalParEx(inherited BalPar);
end;
function TBalanceEx.GetExtra: TValue;
begin
Result := BalPar.Extra;
end;
procedure TBalanceEx.SetExtra(Value: TValue);
begin
BalPar.Extra := Value;
end;
With this approach, there is only one typecast needed and it does not require additional storage.
To enforce TBalanceEx.BalPar to be of type TBalParEx, you could raise an exception in the Initialize routine:
function TBalance.Initialize(ABalPar: TBalPar): Boolean;
begin
FBalPar := ABalPar;
Result := True;
end;
function TBalanceEx.Initialize(ABalPar: TBalPar): Boolean;
begin
if ABalPar is TBalParEx then
Result := inherited Initialize(ABalPar)
else
raise Exception.Create('Wrong BalPar type');
end;
Of course, this places the sole responsibility of a correct class functioning on the requirement to always call the Initialize routine before any other usage of the other class members. Since that is what initialization obviously is intended for, you could ignore that, but protection against misuse could be added like:
TBalance = class(TObject)
protected
function HasBalPar: Boolean; virtual;
...
TBalanceEx = class(TBalance)
protected
function HasBalPar: Boolean; override;
...
function TBalance.HasBalPar: Boolean;
begin
Result := FBalPar is TBalPar;
end;
function TBalance.Initialize(ABalPar: TBalPar): Boolean;
begin
FBalPar := ABalPar;
Result := HasPalBar;
end;
function TBalanceEx.GetExtra: TValue;
begin
if HasBalPar then
Result := BalPar.Extra
else
Result := nil;
end;
function TBalanceEx.HasBalPar: Boolean;
begin
Result := BalPar is TBalParEx;
end;
function TBalanceEx.Initialize(ABalPar: TBalPar): Boolean;
begin
Result := inherited Initialize(ABalPar);
if Result = False then
raise Exception.Create('Initialization went wrong');
end;
procedure TBalanceEx.SetExtra(Value: TValue);
begin
if HasBalPar then
BalPar.Extra := Value;
end;
In turn, this requires not to forget to implement HasBalPar for each derived class. You could 'protect' against that with:
TBalance = class(TObject)
strict private
function HasBalPar: Boolean;
private
...
TBalanceEx = class(TBalance)
strict private
function HasBalPar: Boolean;
private
...
Design considerations
All in all, making this a robust design requires some work. And your current approach raises the question why you would want to have the Extra field in the TBalanceEx class too. Even why to have a TBalanceEx class at all.
From the naming of your classes, I assume you have the following equivalent: A structure which has structural parameters like build date, owner, location, and you have a specialized structure, say a castle, with additional parameters like the number of towers and whether it has a moat:
TStructureData: Location, BuildDate
TCastleData: Location, BuildDate, TowerCount, HasMoat
TStructure: StructureData
TCastle: StructureData, CastleData
The question you need to answer is whether a structure needs to know if it is a castle, or a palace, a warehouse, a biological or chemical structure. Assume your program evolves to being able to handle all different kinds of structures, then you are always bound to add two classes to your program, resulting in a more and more complex and improvised design which in the end will get you in trouble, if not already. The challenge is to make this a more generalized and abstract design.
For example:
must TStructureData and TStructure be separate classes?
could calculations, analysations, or presentational requests on the specific data be 'outsourced' to the specific class? E.g.: if you add a GetFeatures routine to the TStructureData class, then the TStructure class can request the features of a TCastle without knowing it being a Castle.
...
Think big.

Related

Delphi: access properties of Field variable directly

i have a class which contains another class.
Is it possible in Delphi to directly access the properties of the member class?
TNameValue = class
private
FSubName: string;
FSubValue: Integer;
public
property SubName: string read FSubName write FSubName;
property SubValue: Integer read FSubValue write FSubValue;
end;
TParentclass = class(TSomeotherclass)
FNameValue: TNameValue;
public
property NameValue: TNameValue read FNameValue write FNameValue;
end;
procedure TForm.Buttonclick();
begin
Parentclass := TParentclass.Create();
// here i would need to directly access the Property of the member class.
Showmessage(Parentclass.Subname);
end;
I know that i could make properties for alle the properties of the subclass that i want to access, but i have this class in multiple other classes and i don't want to change the code everywhere when the subclass changes.
Is there a way to define the property to publish its properties directly?
I know that i can access it using Parentclass.NameValue.Subname but i want to use it without the additional step of NameValue.
Is there a way to define the property to publish its properties directly?
No this is not possible as you would need multi inheritance to achieve this and Delphi does not support it. Either rework your class design or go through the hassle of implementing the needed properties.
Delphi doesn't automatically recognise that.
But you can help it.
constructor TParentClass.Create(aOwner: TComponent);
begin
inherited Create(aOwner);
fSubClass := TSubClass.Create(Self);
fSubClass.SetSubComponent(True);
end;
like this you create a compound component
a component containing another component
changed from here:
unit uSubClass;
uses Classes;
type
TSubClass = class(TObject)
private
fProp: string;
protected
procedure SetProp(const aValue: string);
function GetProp: string;
public
property Prop: string read GetProp write SetProp;
end;
var SingleSubClass: TSubclass;
implmentation
procedure SetProp(const aValue: string);
begin
fProp := aValue;
end;
function GetProp: string;
begin
Result := fProp;
end;
initialization
SingleSubClass := TSubClass.Create;
finalization
SingleSubClass.Free;
end;
that SingleSubClass is now a global variable and can be accessed in another object.
procedure TForm123.Button1Click(Sender: TObject);
begin
ShowMessage(SingleSubClass.Prop);
end;
if you want other objects to be notified of a change on it, you'll have to add an observer pattern to it and register all interested objects for the changes
https://sourcemaking.com/design_patterns/observer/delphi

How to pass generic procedure TProc<T1,T2> as a parameter and invoke it?

I have a logging class, which links to many modules. The main method of this class is a class method:
type
TSeverity = (seInfo, seWarning, seError);
TLogger = class
class procedure Log(AMessage: String; ASeverity: TSeverity);
end;
Somewhere else I have a function DoSomething() which does some things that I would like to log. However, I do not want to link all the modules of the logger to the module in which 'DoSomething()' is declared to use the logger. Instead I would like to pass an arbitrary logging method as a DoSomething's parameter and call it from its body.
The problem is that TLogger.Log requires parameter of TSeverity type which is defined in logger class. So I can't define a type:
type
TLogProcedure = procedure(AMessage: String; ASverity: TSeverity) of Object;
because I would have to include an unit in which TSeverity is declared.
I was trying to come up with some solution based on generic procedure but I am stuck.
uses
System.SysUtils;
type
TTest = class
public
class function DoSomething<T1, T2>(const ALogProcedure: TProc<T1,T2>): Boolean; overload;
end;
implementation
class function TTest.DoSomething<T1, T2>(const ALogProcedure: TProc<T1, T2>): Boolean;
var
LMessage: String;
LSeverity: Integer;
begin
//Pseudocode here I would like to invoke logging procedure here.
ALogProcedure(T1(LMessage), T2(LSeverity));
end;
Somewehere else in the code I would like to use DoSomething
begin
TTest.DoSomething<String, TSeverity>(Log);
end;
Thanks for help.
Update
Maybe I didn't make myself clear.
unit uDoer;
interface
type
TLogProcedure = procedure(AMessage: String; AErrorLevel: Integer) of Object;
// TDoer knows nothing about logging mechanisms that are used but it allows to pass ALogProcedure as a parameter.
// I thoight that I can somehow generalize this procedure using generics.
type
TDoer = class
public
class function DoSomething(const ALogProcedure: TLogProcedure): Boolean;
end;
implementation
class function TDoer.DoSomething(const ALogProcedure: TLogProcedure): Boolean;
begin
ALogProcedure('test', 1);
Result := True;
end;
end.
Separate unit with one of the logging mechanisms.
unit uLogger;
interface
type
TSeverity = (seInfo, seWarning, seError);
// I know that I could solve my problem by introducing an overloaded method but I don't want to
// do it like this. I thought I can use generics somehow.
TLogger = class
class procedure Log(AMessage: String; ASeverity: TSeverity); {overload;}
{class procedure Log(AMessage: String; ASeverity: Integer); overload;}
end;
implementation
class procedure TLogger.Log(AMessage: String; ASeverity: TSeverity);
begin
//...logging here
end;
{class procedure TLogger.Log(AMessage: String; ASeverity: Integer);
begin
Log(AMessage, TSeverity(ASeverity));
end;}
end.
Sample usage of both units.
implementation
uses
uDoer, uLogger;
procedure TForm10.FormCreate(Sender: TObject);
begin
TDoer.DoSomething(TLogger.Log); //Incompatible types: Integer and TSeverity
end;
Introducing generics here does not help. The actual parameters that you have are not generic. They have fixed type, string and Integer. The function you are passing them to is not generic and receives parameters of type string and TSeverity. These types are mis-matched.
Generics won't help you here because your types are all known ahead of time. There is nothing generic here. What you need to do, somehow, is convert between Integer and TSeverity. Once you can do that then you can call your function.
In your case you should pass a procedure that accepts an Integer, since you don't have TSeverity available at the point where you call the procedure. Then in the implementation of that procedure, where you call the function that does accept a TSeverity, that's where you convert.
In scenarios involving generic procedural types, what you have encountered is quite common. You have a generic procedural type like this:
type
TMyGenericProcedure<T> = procedure(const Arg: T);
In order to call such a procedure you need an instance of T. If you are calling the procedure from a function that is generic on T, then your argument must also be generic. In your case that argument is not generic, it is fixed as Integer. At that point your attempt to use generics unravels.
Having said all of that, what you describe doesn't really hang together at all. How can you possibly come up with the severity argument if you don't know what TSeverity is at that point? That doesn't make any sense to me. How can you just conjure up an integer value and hope that it matches this enumerated type? Some mild re-design would enable you to do this quite simply without any type conversions.
As David Heffernan says, you cannot use generics in this way. Instead you should use a function to map the error level to a severity type, and use that to glue together the two. Based on your updated example, one could modify it like this:
unit uDoer;
interface
type
TLogProcedure = reference to procedure(const AMessage: String; AErrorLevel: Integer);
// TDoer knows nothing about logging mechanisms that are used but it allows to pass ALogProcedure as a parameter.
type
TDoer = class
public
class function DoSomething(const ALogProcedure: TLogProcedure): Boolean;
end;
implementation
class function TDoer.DoSomething(const ALogProcedure: TLogProcedure): Boolean;
begin
ALogProcedure('test', 1);
Result := True;
end;
end.
You can then provide the glue procedure which converts the error level to a severity:
implementation
uses
uDoer, uLogger;
function SeverityFromErrorLevel(const AErrorLevel: Integer): TSeverity;
begin
if (AErrorLevel <= 0) then
result := seInfo
else if (AErrorLevel = 1) then
result := seWarning
else
result := seError;
end;
procedure LogProc(const AMessage: String; AErrorLevel: Integer);
var
severity: TSeverity;
begin
severity := SeverityFromErrorLevel(AErrorLevel);
TLogger.Log(AMessage, severity);
end;
procedure TForm10.FormCreate(Sender: TObject);
begin
TDoer.DoSomething(LogProc);
end;
Note I didn't compile this, but the essence is there. I used a procedure reference (reference to procedure) as they're a lot more flexible, which may come in handy later.

How to access a private field from a class helper in Delphi 10.1 Berlin?

I would like to use Gabriel Corneanu's jpegex, a class helper for jpeg.TJPEGImage.
Reading this and this I've learned that beyond Delphi Seattle you cannot access private fields anymore like jpegex does (FData in the example below). Poking around with the VMT like David Heffernan proposed is far beyond me. Is there any easier way to get this done?
type
// helper to access TJPEGData fields
TJPEGDataHelper = class helper for TJPEGData
function Data: TCustomMemoryStream; inline;
procedure SetData(D: TCustomMemoryStream);
procedure SetSize(W,H: integer);
end;
// TJPEGDataHelper
function TJPEGDataHelper.Data: TCustomMemoryStream;
begin
Result := self.FData;
end;
Today I found a neat way around this bug using the with statement.
function TValueHelper.GetAsInteger: Integer;
begin
with Self do begin
Result := FData.FAsSLong;
end;
end;
Besides that Embarcadero did a nice job building walls to protect the private parts and that's probably why they named it 10.1 Berlin.
Beware! This is a nasty hack and can fail when the internal field structure of the hacked class changes.
type
TJPEGDataHack = class(TSharedImage)
FData: TCustomMemoryStream; // must be at the same relative location as in TJPEGData!
end;
// TJPEGDataHelper
function TJPEGDataHelper.Data: TCustomMemoryStream;
begin
Result := TJPEGDataHack(self).FData;
end;
This will only work if the parent class of the "hack" class is the same as the parent class of the original class. So, in this case, TJPEGData inherits from TSharedImage and so does the "hack" class. The positions also need to match up so if there was a field before FData in the list then an equivalent field should sit in the "hack" class, even if it's not used.
A full description of how it works can be found here:
Hack #5: Access to private fields
By using a combination of a class helper and RTTI, it is possible to have the same performance as previous Delphi versions using class helpers.
The trick is to resolve the offset of the private field at startup using RTTI, and store that inside the helper as a class var.
type
TBase = class(TObject)
private // Or strict private
FMemberVar: integer;
end;
type
TBaseHelper = class helper for TBase // Can be declared in a different unit
private
class var MemberVarOffset: Integer;
function GetMemberVar: Integer;
procedure SetMemberVar(value: Integer);
public
class constructor Create; // Executed automatically at program start
property MemberVar : Integer read GetMemberVar write SetMemberVar;
end;
class constructor TBaseHelper.Create;
var
ctx: TRTTIContext;
begin
MemberVarOffset := ctx.GetType(TBase).GetField('FMemberVar').Offset;
end;
function TBaseHelper.GetMemberVar: Integer;
begin
Result := PInteger(Pointer(NativeInt(Self) + MemberVarOffset))^;
end;
procedure TBaseHelper.SetMemberVar(value: Integer);
begin
PInteger(Pointer(NativeInt(Self) + MemberVarOffset))^ := value;
end;
As you can see it requires a bit of extra typing, but compared to patching a whole unit, it is simple enough.

With a class operator is an implicit typecast to itself allowed?

I have a record that looks like:
TBigint = record
PtrDigits: Pointer; <-- The data is somewhere else.
Size: Byte;
MSB: Byte;
Sign: Shortint;
...
class operator Implicit(a: TBigint): TBigint; <<-- is this allowed?
....
The code is pre-class operator legacy code, but I want to add operators.
I know the data should really be stored in a dynamic array of byte, but I do not want to change the code, because all the meat is in x86-assembly.
I want to following code to trigger the class operator at the bottom:
procedure test(a: TBignum);
var b: TBignum;
begin
b:= a; <<-- naive copy will tangle up the `PtrDigit` pointers.
....
If I add the implicit typecast to itself, will the following code be executed?
class operator TBigint.Implicit(a: TBigint): TBigint;
begin
sdpBigint.CreateBigint(Result, a.Size);
sdpBigint.CopyBigint(a, Result);
end;
(Will test and add the answer if it works as I expect).
My first answer attempts to dissuade against the idea of overriding the assignment operator. I still stand by that answer, because many of the problems to be encountered are better solved with objects.
However, David quite rightly pointed out that TBigInt is implemented as a record to leverage operator overloads. I.e. a := b + c;. This is a very good reason to stick with a record based implementation.
Hence, I propose this alternative solution that kills two birds with one stone:
It removes the memory management risks explained in my other answer.
And provides a simple mechanism to implement Copy-on-Write semantics.
(I do still recommend that unless there's a very good reason to retain a record based solution, consider switching to an object based solution.)
The general idea is as follows:
Define an interface to represent the BigInt data. (This can initially be minimalist and support only control of the pointer - as in my example. This would make the initial conversion of existing code easier.)
Define an implementation of the above interface which will be used by the TBigInt record.
The interface solves the first problem, because interfaces are a managed type; and Delphi will dereference the interface when a record goes out of scope. Hence, the underlying object will destroy itself when no longer needed.
The interface also provides the opportunity to solve the second problem, because we can check the RefCount to know whether we should Copy-On-Write.
Note that long term it might prove beneficial to move some of the BigInt implementation from the record to the class & interface.
The following code is trimmed-down "big int" implementation purely to illustrate the concepts. (I.e. The "big" integer is limited to a regular 32-bit number, and only addition has been implemented.)
type
IBigInt = interface
['{1628BA6F-FA21-41B5-81C7-71C336B80A6B}']
function GetData: Pointer;
function GetSize: Integer;
procedure Realloc(ASize: Integer);
function RefCount: Integer;
end;
type
TBigIntImpl = class(TInterfacedObject, IBigInt)
private
FData: Pointer;
FSize: Integer;
protected
{IBigInt}
function GetData: Pointer;
function GetSize: Integer;
procedure Realloc(ASize: Integer);
function RefCount: Integer;
public
constructor CreateCopy(ASource: IBigInt);
destructor Destroy; override;
end;
type
TBigInt = record
PtrDigits: IBigInt;
constructor CreateFromInt(AValue: Integer);
class operator Implicit(AValue: TBigInt): Integer;
class operator Add(AValue1, AValue2: TBigInt): TBigInt;
procedure Add(AValue: Integer);
strict private
procedure CopyOnWriteSharedData;
end;
{ TBigIntImpl }
constructor TBigIntImpl.CreateCopy(ASource: IBigInt);
begin
Realloc(ASource.GetSize);
Move(ASource.GetData^, FData^, FSize);
end;
destructor TBigIntImpl.Destroy;
begin
FreeMem(FData);
inherited;
end;
function TBigIntImpl.GetData: Pointer;
begin
Result := FData;
end;
function TBigIntImpl.GetSize: Integer;
begin
Result := FSize;
end;
procedure TBigIntImpl.Realloc(ASize: Integer);
begin
ReallocMem(FData, ASize);
FSize := ASize;
end;
function TBigIntImpl.RefCount: Integer;
begin
Result := FRefCount;
end;
{ TBigInt }
class operator TBigInt.Add(AValue1, AValue2: TBigInt): TBigInt;
var
LSum: Integer;
begin
LSum := Integer(AValue1) + Integer(AValue2);
Result.CreateFromInt(LSum);
end;
procedure TBigInt.Add(AValue: Integer);
begin
CopyOnWriteSharedData;
PInteger(PtrDigits.GetData)^ := PInteger(PtrDigits.GetData)^ + AValue;
end;
procedure TBigInt.CopyOnWriteSharedData;
begin
if PtrDigits.RefCount > 1 then
begin
PtrDigits := TBigIntImpl.CreateCopy(PtrDigits);
end;
end;
constructor TBigInt.CreateFromInt(AValue: Integer);
begin
PtrDigits := TBigIntImpl.Create;
PtrDigits.Realloc(SizeOf(Integer));
PInteger(PtrDigits.GetData)^ := AValue;
end;
class operator TBigInt.Implicit(AValue: TBigInt): Integer;
begin
Result := PInteger(AValue.PtrDigits.GetData)^;
end;
The following tests were written as I built up the proposed solution. They prove: some basic functionality, that the copy-on-write works as expected, and that there are no memory leaks.
procedure TTestCopyOnWrite.TestCreateFromInt;
var
LBigInt: TBigInt;
begin
LBigInt.CreateFromInt(123);
CheckEquals(123, LBigInt);
//Dispose(PInteger(LBigInt.PtrDigits)); //I only needed this until I
//started using the interface
end;
procedure TTestCopyOnWrite.TestAssignment;
var
LValue1: TBigInt;
LValue2: TBigInt;
begin
LValue1.CreateFromInt(123);
LValue2 := LValue1;
CheckEquals(123, LValue2);
end;
procedure TTestCopyOnWrite.TestAddMethod;
var
LValue1: TBigInt;
begin
LValue1.CreateFromInt(123);
LValue1.Add(111);
CheckEquals(234, LValue1);
end;
procedure TTestCopyOnWrite.TestOperatorAdd;
var
LValue1: TBigInt;
LValue2: TBigInt;
LActualResult: TBigInt;
begin
LValue1.CreateFromInt(123);
LValue2.CreateFromInt(111);
LActualResult := LValue1 + LValue2;
CheckEquals(234, LActualResult);
end;
procedure TTestCopyOnWrite.TestCopyOnWrite;
var
LValue1: TBigInt;
LValue2: TBigInt;
begin
LValue1.CreateFromInt(123);
LValue2 := LValue1;
LValue1.Add(111); { If CopyOnWrite, then LValue2 should not change }
CheckEquals(234, LValue1);
CheckEquals(123, LValue2);
end;
Edit
Added a test demonstrating use of TBigInt as value parameter to a procedure.
procedure TTestCopyOnWrite.TestValueParameter;
procedure CheckValueParameter(ABigInt: TBigInt);
begin
CheckEquals(2, ABigInt.PtrDigits.RefCount);
CheckEquals(123, ABigInt);
ABigInt.Add(111);
CheckEquals(234, ABigInt);
CheckEquals(1, ABigInt.PtrDigits.RefCount);
end;
var
LValue: TBigInt;
begin
LValue.CreateFromInt(123);
CheckValueParameter(LValue);
end;
There is nothing in Delphi that allows you to hook into the assignment process. Delphi has nothing like C++ copy constructors.
Your requirements, are that:
You need a reference to the data, since it is of variable length.
You also have a need for value semantics.
The only types that meet both of those requirements are the native Delphi string types. They are implemented as a reference. But the copy-on-write behaviour that they have gives them value semantics. Since you want an array of bytes, AnsiString is the string type that meets your needs.
Another option would be to simply make your type be immutable. That would let you stop worrying about copying references since the referenced data could never be modified.
It seems to me your TBigInt should be a class rather than a record. Because you're concerned about PtrDigits being tangled up, it sounds like you're needing extra memory management for what the pointer references. Since records don't support destructors there's no automatic management of that memory. Also if you simply declare a variable of TBigInt, but don't call the CreatBigInt constructor, the memory is not correctly initialised. Again, this is because you cannot override a record's default parameterless constructor.
Basically you have to always remember what has been allocated for the record and remember to manually deallocate. Sure you can have a deallocate procedure on the record to help in this regard, but you still have to remember to call it in the correct places.
However that said, you could implement an explicit Copy function, and add an item to your code-review checklist that TBitInt has been copied correctly. Unfortunately you'll have to be very careful with the implied copies such as passing the record via a value parameter to another routine.
The following code illustrates an example conceptually similar to your needs and demonstrates how the CreateCopy function "untangles" the pointer. It also highlights some of the memory management problems that crop up, which is why records are probably not a good way to go.
type
TMyRec = record
A: PInteger;
function CreateCopy: TMyRec;
end;
function TMyRec.CreateCopy: TMyRec;
begin
New(Result.A);
Result.A^ := A^;
end;
var
R1, R2: TMyRec;
begin
New(R1.A); { I have to manually allocate memory for the pointer
before I can use the reocrd properly.
Even if I implement a record constructor to assist, I
still have to remember to call it. }
R1.A^ := 1;
R2 := R1;
R2.A^ := 2; //also changes R1.A^ because pointer is the same (or "tangled")
Writeln(R1.A^);
R2 := R1.CreateCopy;
R2.A^ := 3; //Now R1.A is different pointer so R1.A^ is unchanged
Writeln(R1.A^);
Dispose(R1.A);
Dispose(R2.A); { <-- Note that I have to remember to Dispose the additional
pointer that was allocated in CreateCopy }
end;
In a nutshell, it seems you're trying to sledgehammer records into doing things they're not really suited to doing.
They are great at making exact copies. They have simple memory management: Declare a record variable, and all memory is allocated. Variable goes out of scope and all memory is deallocated.
Edit
An example of how overriding the assignment operator can cause a memory leak.
var
LBigInt: TBigInt;
begin
LBigInt.SetValue(123);
WriteBigInt(LBigInt); { Passing the value by reference or by value depends
on how WriteBigInt is declared. }
end;
procedure WriteBigInt(ABigInt: TBigInt);
//ABigInt is a value parameter.
//This means it will be copied.
//It must use the overridden assignment operator,
// otherwise the point of the override is defeated.
begin
Writeln('The value is: ', ABigInt.ToString);
end;
//If the assignment overload allocated memory, this is the only place where an
//appropriate reference exists to deallocate.
//However, the very last thing you want to do is have method like this calling
//a cleanup routine to deallocate the memory....
//Not only would this litter your code with extra calls to accommodate a
//problematic design, would also create a risk that a simple change to taking
//ABigInt as a const parameter could suddenly lead to Access Violations.

which is the best way to control a object-field type life-cycle?

TMyClass = class(TObject)
private
FMyObject: TObject;
function GetMyObject: TObject;
public
property MyObject: TObject read GetMyObject write FMyObject;
end;
function TMyClass.GetMyObject: TObject;
begin
if FMyObject = nil then
FMyObject := TObject.Create;
Result := FMyObject;
end;
Sometimes, "MyObject" is not created internally but externally created and assigned to the parameter. If this object is created externally, I can not free it in this context.
Should I create a TList and Add in all objects that were created internally and destroy everything on the destructor?
How can I control the lifetime of a parameter if it is created internally or not? What you suggest to do? Is there any pattern to do this?
I'd set a flag in the Property Setter
procedure TMyClass.SetMyObject(AObject: TObject);
begin
if Assigned(MyObject) and FIsMyObject then
FMyObject.Free;
FIsMyObject := False;
FMyObject := AObject;
end;
function TMyClass.GetMyObject: TObject;
begin
if FMyObject = nil then
begin
FMyObject := TObject.Create;
FIsMyObject := True;
end;
Result := FMyObject;
end;
Destructor TMyClass.Destroy;
begin
if FIsMyObject then
FMyObject.Free;
end;
I guess the best would be to redesign your code so that this problem won't arise - this kind of ownership ambiguity is a mess.
Anyway, one option would be to use (reference counted) interfaces. This is problematic in case of circular references.
If the externally created object must not be the only reference then you could still create internal copy of the object, something like
procedure TMyClass.SetMyObject(const Value: TObject);
begin
MyObject.Assign(Value);
end;
You could assign external object to different field than internal and then you don't Free that field in destructor. Or set a flag in the property setter so that you know not to free the external object...
The two most logical and practical solutions (keep a flag, copy on assignment) are already given, but for completeness sake and since the object field isn't likely to be of the TObject type, here are three other approaches. The practicality of these depends on the type of the object field, whether you really don't want an extra boolean flag and whether you dislike to add some intelligent behavior to this construction.
(Warning: this may be a little farfetched.)
Test if the object field is of your private object type:
property MyObject: TSomeAncestor read GetMyObject write SetMyObject;
end;
implementation
type
TMyObject = class(TSomeAncestor) ... end;
destructor TMyClass.Destroy;
begin
if FMyObject is TMyObject then
FMyObject.Free;
Test on the ownership of the object field:
property MyObject: TOwnedObject read GetMyObject write SetMyObject;
end;
implementation
destructor TMyClass.Destroy;
begin
if FMyObject.Owner = Self then
FMyObject.Free;
This construction is especially useful if the external object should anyway be freed by this class: just set its Owner to this class instance. The decision depends no longer on the internal or external creation of the object.
If the object field descends from TComponent, then you do not have to free at all.

Resources