17

This is similar to this question. I asked "Why?" to the most popular response but I don't know that anyone would ever look at it again. At least not in any timely manner.

Anyway, my question is about best practices for delegating responsibility for creation of objects to functions or procedures, without causing memory leaks. It seems that this:

procedure FillObject(MyObject: TMyObject; SomeParam: Integer);
begin
  //Database operations to fill object
end;

procedure CallUsingProcedure();
var
  MyObject: TMyObject;
begin
  MyObject = TMyObject.Create();
  try
    FillObject(MyObject, 1);
    //use object
  finally
    MyObject.Free();
  end;
end;

is preferred over this:

function CreateMyObject(DBID: Integer): TMyObject;
begin
  Result := TMyObject.Create();
  try
    //Database operations to fill object
  except on E: Exception do
    begin
      Result.Free();
      raise;
    end;
  end;
end;

procedure CallUsingFunction();
var
  MyObject: TMyObject;
begin
  MyObject = CreateMyObject(1);
  try
    //use object
  finally
    MyObject.Free();
  end;
end;

Why?

I'm relatively new to Delphi, having previously worked most with Java and PHP, as well as C++, though to a lesser extent. Intuitively, I lean toward the function method because:

  • It encapsulates the object creation code in the function, rather than create the object separately whenever I want to use the procedure.
  • I dislike methods that alter their parameters. It's often left undocumented and can make tracing bugs more difficult.
  • Vague, but admittedly it just "smells" bad to me.

I'm not saying I'm right. I just want to understand why the community chooses this method and if there is good reason for me to change.

Edit: References to @E-Rock in comments are to me(Eric G). I changed my display name.

Community
  • 1
  • 1
Eric G
  • 3,427
  • 5
  • 28
  • 52
  • 1
    Your second case, in effect, functions like an overloaded constructor, I have no idea why would I prefer the first case over that. FWIW, the first case also uses a *function* to retrieve an instance: the constructor. – Sertac Akyuz Sep 14 '11 at 22:53
  • 2
    What value does your helper function `CreateMyObject` have over putting that database logic in a special second constructor `CreateAndFillWithDbData`? I would say that your problem is not just returning an object which is unclear where to free (ken's point is valid!) but also that you have abandoned Object Oriented Programming – Warren P Sep 14 '11 at 23:02
  • @Warren - You maybe have a point, as I'm simulating things the best way I know how in Delphi. I don't want TMyObject to know about any details of the databases. In this case, I'm filling it with values in one place, and passing them to another. TMyObject contains only properties and and fields. It doesn't hold any methods. So abandoned OOP, no. But in my zeal to keep classes out of other classes business, it's possible I chose the wrong tool. ;-) Thoughts? Cause in many cases, the overloaded constructor would solve the problem. – Eric G Sep 14 '11 at 23:20
  • 1
    @E-Rock You are absolutely correct. Separating concerns in the way you have done (e.g. database vs business logic) is good practise. Mixing concerns as Warren P suggests is just a sure way to create brittle classes that can't be reused due to their many dependencies. – awmross Sep 14 '11 at 23:38
  • 2
    I agree with the responsibility for memory management issues and with the descendent issue mentioned in another answer. As the class contains only fields and properties I would use a constructor that took the values from the database as parameters, which would keep it decoupled from the database, but still allow construction and population in one call. Alternatively you could create a descendent of TMyObject that is coupled to the database. TMyObject would still be decoupled and reusable, but you could encapsulate the population code within the descendent. – Richard A Sep 15 '11 at 02:43

3 Answers3

15

One problem is what Ken White wrote: you hand the user of the function an object he or she must free.

Another advantage of procedures is that you can pass several objects of a hierarchy, while a function that creates such an object always generates the same. E.g.

procedure PopulateStrings(Strings: TStrings);

To that procedure, you can pass any kind of TStrings, be it the Lines of a TMemo, the Items of a TListBox or TComboBox or a simple standalone TStringList. If you have a function:

function CreateStrings: TStrings;

You always get the same kind of object back (which object exactly is not known, as TStrings is abstract, so you probably get a TStringList), and must Assign() the contents to the TStrings you want to modify. The procedure is to be preferred, IMO.

Additionally, if you are the author of the function, you can't control whether the object you create is freed, or when. If you write a procedure, that problem is taken off your hands, since the user provides the object, and its lifetime is none of your concern. And you don't have to know the exact type of the object, it must just be of the class or a descendant of the parameter. IOW, it is also much better for the author of the function.

It is IMO seldom a good idea to return an object from a function, for all the reasons given. A procedure that only modifies the object has no dependency on the object and creates no dependency for the user.

FWIW, Another problem is if you do that from a DLL. The object returned uses the memory manager of the DLL, and also the VMT to which it points is in the DLL. That means that code that uses as or is in the user code does not work properly (since is and as use the VMT pointer to check for class identity). If the user must pass an object of his, to a procedure, that problem does not arise.

Update

As others commented, passing an object to a DLL is not a good idea either. Non-virtual functions will call the functions inside the DLL and use its memory manager, which can cause troubles too. And is and as will not work properly inside the DLL either. So simply don't pass objects into or out of a DLL. That goes with the maxime that DLLs should only use POD type parameters (or compound types -- arrays, records -- that only contain POD types) or COM interfaces. The COM interfaces should also only use the same kind of parameters.

Rudy Velthuis
  • 28,387
  • 5
  • 46
  • 94
  • +1 for catching the descendent issue (I rely on it quite a bit in my own code, and omitted it when answering). `TStrings` is a particularly good example. – Ken White Sep 14 '11 at 23:23
  • 3
    Passing an object *to* a DLL can be just as bad. If the DLL calls any non-virtual functions on that object, the code will go to the DLL's version of the function, so you again end up using the wrong memory manager or possibly even the wrong version of the RTL, and `is` and `as` will continue to fail. – Rob Kennedy Sep 14 '11 at 23:53
  • memory manager is tip of the iceberg when it comes to sharing objects between DLLs – David Heffernan Sep 15 '11 at 06:19
  • @Rob: Agreed. I left it out for the sake of brevity. – Rudy Velthuis Sep 15 '11 at 09:10
13

Creating the object instance and passing it into another procedure makes it clear which code is responsible for freeing the instance.

In the first case (using a procedure to fill it):

MyObj := TMyObject.Create;
try
  // Do whatever with MyObj
finally
  MyObj.Free;
end;

This is clear that this block of code is responsible for freeing MyObj when it's finished being used.

MyObj := CreateMyObject(DBID);

What code is supposed to free it? When can you safely free it? Who is responsible for exception handling? How do you know (as a user of someone else's code)?

As a general rule, you should create, use, and free object instances where they're needed. This makes your code easier to maintain, and definitely makes it easier for someone who comes along later and has to try and figure it out. :)

Ken White
  • 123,280
  • 14
  • 225
  • 444
  • 2
    While I mostly agree with this, it's clearly called `CreateMyObject` which implies ... a new object created and ownership transferred to caller (to me). There may be things that need to be done during the creation turning this approach into a "factory method" of sorts. –  Sep 14 '11 at 22:30
  • You're right. For my "bad" example, I chose a poor name for the example. Edited - thanks for pointing it out. – Ken White Sep 14 '11 at 22:38
  • Slightly unfair, your function is `PopulateData` but code in the question names the function `CreateMyObject` making it clear that it will create an object. – Sertac Akyuz Sep 14 '11 at 23:28
  • In one case, you need to know who owns it, which just requires a little documentation or well-understood naming conventions. In the other, you need to know what PopulateData does, which just requires a little documentation or well-understood naming conventions. It kinda seems like a lesser of two evils thing. Is clarity on object ownership just considered more important than clarity on what a function does? – Eric G Sep 14 '11 at 23:30
  • 2
    Wow. People seem to be concentrating more on the names I chose rather than the content of the text. :) Edited to more closely resemble the original question's naming/use. @E-Rock: They're both important, but to me clarity of the responsibility for memory/resource management comes first, as they can have a system-wide impact. – Ken White Sep 14 '11 at 23:35
  • Is it the de facto standard in the community to use procedures? I'll probably go with the procedures cause the number one thing that bugs me is non-standard coding practices, so I'm happy to conform. I'm just wonder if CreateObject methods could be non-standard or just less common. (This is just me probing, trying to get to know the community.) – Eric G Sep 14 '11 at 23:56
  • 5
    No, E-Rock. Your question presupposed something to be true that hadn't been proved yet. I prefer the factory-function technique when the goal is to construct a new object. If the goal is to use an existing object, then I'll use the procedure technique. They both have their uses. The factory method may indeed be less common. It's certainly less common to be done *right* (i.e., with proper exception handling). – Rob Kennedy Sep 15 '11 at 00:07
  • When you say "with proper exception handling", do you mean catching exceptions to free the object, as in my example? Or is there something in excess of that to be aware of? – Eric G Sep 15 '11 at 00:26
  • 1
    Yes, that's what I mean. Naive implementations neglect to free the result, or quell the exception and return a null pointer. – Rob Kennedy Sep 15 '11 at 03:39
6

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.
  • 28,486
  • 9
  • 71
  • 95