tags:

views:

269

answers:

8

Here I provide simple piece of code.

function GetStringList:TStringList;
var i:integer;
begin
   Result:=TStringList.Create;
   Result.Add('Adam');
   Result.Add('Eva');
   Result.Add('Kain');
   Result.Add('Abel');
end;


procedure ProvideStringList(SL:TStringList);
var i:integer;
    Names:TStringList;
begin
   Names:=TStringList.Create;
   Names.Add('Adam');
   Names.Add('Eva');
   Names.Add('Kain');
   Names.Add('Abel');
   SL.Assign(Names);
   Names.Free;
end;


procedure TForm1.btn1Click(Sender: TObject);
var SL:TStringList;
    i:integer;
begin
   SL:=TStringList.Create;
   SL.Assign(GetStringList);
   for i:=0 to 3 do ShowMessage(SL[i]);
   SL.Free;
end;


procedure TForm1.btn2Click(Sender: TObject);
var SL:TStringList;
    i:integer;
begin
   SL:=TStringList.Create;
   ProvideStringList(SL);
   for i:=0 to 3 do ShowMessage(SL[i]);
   SL.Free;
end;

And now the question: what will happen to result object in function GetStringList:Tstringlist, which is created, but never freed? (I call 2 times Create and only 1 time Free) Is it memory safe to provide objects by function or should I use procedures to do this task, where object creation and destroying is simply handled (procedure ProvideStringlist)? I call 2 times Create and 2 times Free. Or is there another solution?

Thanx in advance

Lyborko

+4  A: 

I don't know what you mean by safe, but it is common practice. The caller of the function becomes responsible for freeing the returned object:

var
   s : TStringList; 
begin
   s := GetStringList;
   // stuff
   s.free;
end;
anon
Thanks, it's easy. I was influenced by common task like:Memo1.Tag:=StrToInt('100'); That's why I was preoccupied to make similar function like:Memo1.Lines.Assign(GetStringList);
lyborko
And note that you should not do that: Memo1.Lines.Assign(GetStringList); because that will create a memory leak, so do what Neil said, and do your assignment where he as "// stuff".
Lasse V. Karlsen
yes, yes... I keep it on mind...
lyborko
This can result in strange code in GetStringList. See http://stackoverflow.com/questions/1894983/why-use-exception-handling-in-apparently-safe-code I prefer http://stackoverflow.com/questions/1894217/is-it-memory-safe-to-provide-an-object-as-a-function-result/1894376#1894376
Lars Truijens
You should probably add a `try...finally` block here. Besides, it's even more common practice to let the caller create the object and pass it to the method to have `Create` and `Free` together.
Smasher
A: 

It is the usage that is the leak, not the construct itself.

var sl2 : TStringlist;

sl2:=GetStringList;
sl.assign(sl2);
sl2.free;

is perfectly fine, or easier even,

sl:=getstringlist;
// no assign, thus no copy, one created one freed.
sl.free;
Marco van de Voort
A: 

In btn1Click you should do:

var sl2: TStringList;

sl2 := GetStringList:
SL.Assign(sl2);
sl2.Free;

In btn2Click you don't have to create an instance of SL before calling ProvideStringList to not create a memory leak.

eKek0
A: 

No, it is not "memory safe". When you create an object, someone has to free it.

Your first example leaks memory:

SL:=TStringList.Create;
SL.Assign(GetStringList);   // <-- The return value of GetStringList is 
                            //     used, but not freed.
for i:=0 to 3 do ShowMessage(SL[i]);
SL.Free;

The second example works fine, but you don't have to create and free an additional temporary instance (Names)

In general, the second example is slightly better, because it is obvious, who is responsible for the creation and destruction of the list. (The caller) In other situations, a returned object must be freed by the caller or perhaps it's forbidden. You can't tell from the code. If you must do so, it's good practice to name your methods accordingly. (CreateList is better than GetList).

DR
Thanks for remarks... :-) This simplicity arised from my misconception. See comment above...
lyborko
+6  A: 

Is it memory safe to provide an object as a function result?

It is possible, but it needs attention from the implementor and the call.

  1. Make it clear for the caller, the he controls the lifetime of the returned object
  2. Make shure you don't have a memory leak when the function fails.

For example:

function CreateBibleNames: TStrings;
begin
  Result := TStringList.Create;
  try
    Result.Add('Adam');
    Result.Add('Eva');
    Result.Add('Kain');
    Result.Add('Abel');
  except
    Result.Free;
    raise;
  end;      
end;

But in Delphi the most commen pattern for this is:

procedure GetBibleNames(Names: TStrings);
begin
  Names.BeginUpdate;
  try
    //perhaps a Names.Clear here
    //but I don't use it often because the other
    //way is more flexible for the caller

    Names.Add('Adam');
    Names.Add('Eva');
    Names.Add('Kain');
    Names.Add('Abel');
  finally
    Names.EndUpdate;
  end;      
end;

so the caller code can look like this:

procedure TForm1.btn1Click(Sender: TObject);
var
  Names: TStrings;
  i:integer;
begin
  Names := CreateBibleNames;
  try
    for i := 0 to Names.Count -1 do 
      ShowMessage(Names[i]);
  finally 
    Names.Free;
  end;
end;

and the other, more common version:

procedure TForm1.btn1Click(Sender: TObject);
var
  Names: TStrings;
  i:integer;
begin
  Names := TStringList.Create;
  try
    GetBibleNames(Names);
    for i := 0 to Names.Count -1 do 
      ShowMessage(Names[i]);
  finally 
    Names.Free;
  end;
end;

(I have no compiler at the moment, so perhaps there are some errors)

Heinz Z.
Very well written, and I'd like to emphasise the point about being careful with the names. `Create` implies the caller has responsibility for the lifetime of the object. `Get` implies the caller doesn't have to worry (for whatever reason).
Craig Young
A: 

I use a combination of both idioms. Pass the object as an optional parameter and if not passed, create the object. And in either case return the object as the function result.

This technique has (1) the flexibility of the creation of the object inside of the called function, and (2) the caller control of the caller passing the object as a parameter. Control in two meanings: control in the real type of the object being used, and control about the moment when to free the object.

This simple piece of code exemplifies this idiom.

function MakeList(aList:TStrings = nil):TStrings;
 var s:TStrings;
 begin
   s:=aList;
   if s=nil then 
     s:=TSTringList.Create;
   s.Add('Adam');
   s.Add('Eva');
   result:=s;
 end;

And here are three different ways to use it

simplest usage, for quick and dirty code

var sl1,sl2,sl3:TStrings;
sl1:=MakeList;

when programmer wants to make more explicit ownership and/or use a custom type

sl2:=MakeList(TMyStringsList.create);

when the object is previously created

sl3:=TMyStringList.Create;
....
MakeList(sl3);
PA
interesting....
lyborko
+1  A: 

Memory safety is a stricter variant of type safety. For memory safety, you typically need a precise garbage collector and a type system which prevents certain kinds of typecasts and pointer arithmetic. By this metric, Delphi is not memory safe, whether you write functions returning objects or not.

Barry Kelly
Oh really? Memory is one of many resources used in a programming; and a _garbage collector_ is **not** a silver-bullet! Also: "Memory safety is a stricter variant of type safety"???? A garbage collector needs to visit that statement!
Craig Young
Yes really. We're talking about CS definitions here: either precise garbage collection, fat pointers (which remember allocation status), infinite memory (i.e. no deallocation), or no memory allocations is required for memory safety, in the absence of things like theorem proving (which tends to turn into the halting problem very quickly in Turing complete languages). You might find http://en.wikipedia.org/wiki/Memory_safety interesting.
Barry Kelly
+1  A: 

These are the very kinds of questions I grappled with in my early days of Delphi. I suggest you take your time with it:

  • write test code with debug output
  • trace your code step-by-step
  • try different options and code constructs
  • and make sure you understand the nuances properly;

The effort will prove a great help in writing robust code.

Some comments on your sample code...

  1. You should get into the habit of always using resource protection in your code, even in simple examples; and especially since your question pertains to memory (resource) protection.
  2. If you name a function GetXXX, then there's no reason for anyone to suspect that it's going to create something, and they're unlikely to protect the resource. So careful naming of methods is extremely important.
  3. Whenever you call a method that creates something, assume it's your responsibility to destroy it.
  4. I noticed some code that would produce Hints from the compiler. I recommend you always eliminate ALL Hints & Warnings in your programs.
    • At best a Hint just means some arbitrary redundant code (excesses of which make maintenance more difficult). More likely it implies you haven't finished something, or rushed it and haven't finished testing/checking.
    • A Warning should always be taken seriously. Even though sometimes the compiler's concern is a logical impossibility in the specific situation, the warning may indicate some subtle language nuance that you're not aware of. The code can always be rewritten in a more robust fashion.
    • I have seen many examples of poor resource protection where there is a compiler warning giving a clue as to the problem. So check them out, it will aid in the learning.

If an exception is raised in a method that returns a new object, care should be taken to ensure there isn't a memory leak as a result.

//function GetStringList:TStringList;
function CreateStringList:TStringList; //Rename method lest it be misinterpreted.
//var i: Integer; You don't use i, so why declare it? Get rid of it and eliminate your Hints and Warnings!
begin
  Result := TStringList.Create;
  try //Protect the memory until this method is done; as it can **only** be done by **this** method!
    Result.Add('Adam');
    Result.Add('Eva');
    Result.Add('Kain');
    Result.Add('Abel');
  except
    Result.Destroy;  //Note Destroy is fine because you would not get here if the line: Result := TStringList.Create; failed.
    raise; //Very important to re-raise the exception, otherwise caller thinks the method was successful.
  end;
end;

A better name for the following would be PopulateStringList or LoadStringList. Again, resource protection is required, but there is a simpler option as well.

procedure ProvideStringList(SL:TStringList);
var //i:integer;  You don't use i, so why declare it? Get rid of it and eliminate your Hints and Warnings!
    Names:TStringList;
begin
  Names:=TStringList.Create;
  try //ALWAYS protect local resources!
    Names.Add('Adam');
    Names.Add('Eva');
    Names.Add('Kain');
    Names.Add('Abel');
    SL.Assign(Names);
  finally //Finally is the correct choice here
    Names.Free; //Destroy would also be okay.
  end;
end;

However; in the above code, creating a temporary stringlist is overkill when you could just add the strings directly to the input object.
Depending on how the input stringlist is used, it is usually advisable to enclose a BeginUpdate/EndUpdate so that the changes can be handled as a batch (for performance reasons). If your method is general purpose, then you have no idea of the origin of the input, so you should definitely take the precaution.

procedure PopulateStringList(SL:TStringList);
begin
  SL.BeginUpdate;
  try //YES BeginUpdate must be protected like a resource
    SL.Add('Adam');
    SL.Add('Eva');
    SL.Add('Kain');
    SL.Add('Abel');
  finally
    SL.EndUpdate;
  end;
end;

our original code below had a memory leak because it called a method to create an object, but did not destroy. However, because the method that created the object was called GetStringList, the error is not immediately obvious.

procedure TForm1.btn1Click(Sender: TObject);
var SL:TStringList;
    i:integer;
begin
  //SL:=TStringList.Create; This is wrong, your GetStringList method creates the object for you.
  //SL.Assign(GetStringList);
  SL := CreateStringList;  //I also used the improved name here.
  try //Don't forget resource protection.
    for i:=0 to 3 do ShowMessage(SL[i]);
  finally
    SL.Free;
  end;
end;

The only error in your final snippet was the lack of resource protection. The technique used is quite acceptable, but may not be ideally suited to all problems; so it helps to also be familiar with the previous technique.

procedure TForm1.btn2Click(Sender: TObject);
var SL:TStringList;
    i:integer;
begin
  SL:=TStringList.Create;
  try //Be like a nun (Get in the habit)
    ProvideStringList(SL);
    for i:=0 to 3 do ShowMessage(SL[i]);
  finally
    SL.Free;
  end;
end;
Craig Young
Craig, my answer only relates to the term memory safety, and I didn't say GC is a silver bullet - though it's almost always better than the alternatives. You might want to dig a little deeper into who's saying what before you suggest what they write is garbage.
Barry Kelly
Craig, Thanks for elaborated reply. I did not expected such storm of answers here, that every single word is so properly investigated.:-). Nevertheless I take your words as a good advice for future...
lyborko