The title might seem a little confusing, but the heart of my question is: What happens to the memory allocated in both of the following scenarios?
var
FHTTPResponse: IHTTPResponse;
...
FHTTPResponse := HTTPClient.Post(RequestURL, TStringStream.Create(APostParams.ToString, TEncoding.UTF8));
The TStringStream
object is created in the parameters list of a function (in this case, THTTPClient.Post
). What happens with the memory allocated for it, as I created it but (afaik) I'm not able to free it.
ExampleClass = class
private
FName, FValue: string;
public
constructor Create(AName, AValue: string);
function AsStringList: TStringList;
end;
implementation
function ExampleClass.AsStringList: TStringList;
begin
Result := TStringList.Create;
Result.Add(FValue);
Result.Add(FName);
end;
constructor ExampleClass.Create(AName, AValue: string);
begin
FValue := AValue;
FName := AName;
end;
When the user calls ExampleClass.AsStringList
, my understanding is that I'm not returning a reference to an object, like I would if I had a field FAsStringList
, but the object itself.
In this scenario, I'd be passing the responsibility of freeing the TStringList
object (which feels just wrong). Does anything else happen underneath this return? Is there a better approach to return the data as different structures, that doesn't imply giving the user the responsibility of freeing the object without them even knowing they have to do so?
TL;DR Is there a better (or a correct) approach to returning data representation as different object types without transferring memory management responsibilities to the user?
Object-type variables/parameters/returns/etc in Delphi are references to objects, not actual objects themselves. Objects reside only in dynamic memory and cannot be passed around by value, only by reference.
Any call to Create()
an object type must have a matching call to Free()
(or Destroy()
) to free the object from memory, otherwise the object is leaked.
So, in Scenario 1, the TStringStream
is leaked if HTTPClient.Post()
does not take ownership of it and free it. And in Scenario 2, the TStringList
is leaked if the caller of AsStringList()
does not take ownership of the returned list and free it.
In Scenario 1, let's assume Post()
does not take ownership of the stream (seems like a reasonable assumption). The caller should maintain ownership of the stream and free it after Post()
returns, eg:
var
FHTTPResponse: IHTTPResponse;
Strm: TStringStream;
...
Strm := TStringStream.Create(APostParams.ToString, TEncoding.UTF8);
try
FHTTPResponse := HTTPClient.Post(RequestURL, Strm);
finally
Strm.Free;
end;
In Scenario 2, the caller must take ownership and free the returned TStringList
, eg:
var
instance: ExampleClass;
list: TStringList;
...
list := instance.AsStringList;
try
...
finally
list.Free;
end;
If you don't want to transfer ownership of the TStringList
to the caller, then ExampleClass
must maintain ownership of it internally, eg:
type
ExampleClass = class
private
FName, FValue: string;
FList: TStringList;
public
constructor Create(AName, AValue: string);
destructor Destroy; override;
function AsStringList: TStringList;
end;
implementation
constructor ExampleClass.Create(AName, AValue: string);
begin
FValue := AValue;
FName := AName;
FList := nil;
end;
destructor ExampleClass.Destroy;
begin
FList.Free;
inherited Destroy;
end;
function ExampleClass.AsStringList: TStringList;
begin
if FList = nil then
FList := TStringList.Create
else
FList.Clear;
FList.Add(FValue);
FList.Add(FName);
Result := FList;
end;
Otherwise, the caller would need to create its own list and pass it to the class to fill in, eg:
type
ExampleClass = class
private
...
public
...
procedure AsStringList(AList: TStrings);
end;
implementation
procedure ExampleClass.AsStringList(AList: TStrings);
begin
AList.Add(FValue);
AList.Add(FName);
end;
var
instance: ExampleClass;
list: TStringList;
...
list := TStringList.Create;
try
...
instance.AsStringList(list);
...
finally
list.Free;
end;