Board index » delphi » Are constructor and destructor required for EVERY object?

Are constructor and destructor required for EVERY object?

Although I have been programming for several years, I am just starting to use
objects.

The help files are pretty skimpy and I would like same real people to point out
some pointers on how to use objects.

Questions:
#1: Do they save memory? {newing and disposing}
#2: Are "constructor Init; destructor Done;" always required?
        #2b. If so, is there a simpler way to do it?
#3: How can I access the fields {the object in declared in another unit} from
another unit?

Any help would be appreciated.

--
AIBrain Home Page -"Artificially Intelligent Brain"
http://www.geocities.com/CapeCanaveral/Lab/7677/index.html

"You see, it all began one stormy night. The sun was shining
and there wasn't a cloud in the sky..." -aibr...@usa.net

"I may or may not have written what I meant or did not mean.
What I meant or did not mean might or might not have been what
I was or wasn't thinking at that moment." -aibr...@usa.net

 

Re:Are constructor and destructor required for EVERY object?


AIBrain <ab...@localhost.net> wrote in article
<3450966D.EBC54...@localhost.net>...

Quote
> Although I have been programming for several years, I am just starting to
use
> objects.

I've used them for a couple of years and I love 'em. I'll try to restrain
my enthusiasm.

Quote
> Questions:
> #1: Do they save memory? {newing and disposing}

An object will take as much space as a record with the same fields, except
that if you have virtual methods there is also a "virtual method pointer"
(actually a 16-bit offset) in there as well. I don't see how it could save
memory, particularly (I suppose if you reuse object methods, you save some
code memory) but that's not really what objects are about. Their advantages
are more to do with how easily the program can be broken into independent
sections, which helps with maintenance and suchlike. It's a bit like units,
except that you only have one instance of a unit whereas you normally have
multiple instances of an object.

Quote
> #2: Are "constructor Init; destructor Done;" always required?

No. You only need constructors and destructors if you have virtual methods.
I've used objects with no virtual methods occasionally - either where the
objects are for a particular purpose and I don't intend to create
descendants, or where the objects are records (e.g.to be read from disc)
and I simply want to make some of the code that manipulates them a bit
neater. Objects with only "Static" methods are a _lot_ like records with
procedures or functions that operate on them, except the code is a little
neater, compare:

Type
  tO1:Object
    a: integer;
    Procedure SetA(x:integer);
  End;
  pr = ^r;
  r:Record
    a:integer;
  End;
Procedure tO1.SetA(x:Integer);
Begin
  a := x;  
End;
Procedure RSetA(p:pR; x:Integer);
Begin
  p^.a := x;
End;

These are really the same except that in the Record case you have to use
the pointer explicitly. This not only clutters the code but also compiles
to less efficient object code because the compiler _expects_ to be using
the "self" pointer  a lot so it optimises a bit. Also the identifier SetA
is local to the object o so you can reuse that name elsewhere. There isn't
much difference in this simple example but if you had a lot of data fields
to manipulate...

On the other hand, if you have one or more virtual methods - and most of
the "fun" with objects comes through virtual methods - then you need a
constructor. And if you have one of those you probably should have a
virtual destructor, because then whatever code you use will call the
correct destructor for whatever object they end up using (which may not be
what they _think_ they're using, which is the whole point of polymorphism
in objects. They don't have to be called Init and Done, but since
descendant types will usually need to call their ancestor's constructor and
destructor, it's usually a good idea to be consistent, which means using
the standard names.

Even if you have virtual methods, if you are creating a descendant type you
don't need to create a new constructor or destructor if they don't do
anything new. The previous ones will be inherited. The compiler has to know
the type of the object being constructed but this will always be the case.

Quote
>    #2b. If so, is there a simpler way to do it?

Objects don't have to be on the heap. There's nothing wrong with having
them as local variables. But then you simply call the constructor and
destructor as ordinary methods:

var
  o2 : tO2;
Begin
  o2.Init;
  ...
  o2.Done;
End;

If you are creating them on the heap, there is a slightly simplified
version of "New" - you can call the constructor in the same line of code as
you create the object:

Type
  ptO3 = ^tO3;
  tO3 = Object
    Constructor Init;
    Destructor Done; Virtual;
  End;

var
  po3 : ptO3;
Begin
  po3 := New(ptO3, Init); { NB ptO3, the type }
  ...
  Dispose(po3, Done);  { NB pO3, the instance }
End;

The New needs to be told the exact type to construct. If that type inherits
its constructor from its ancestor, and does not have an explicit
constructor of its own, the virtual method table link still needs to point
to tO3's table, not that of its ancestor.

Note: tO3 is a new object, which is not descended from anything. In
particular I'm not descending tO3 out of tObject. You don't have to, unless
you're using code that expects a descendant of tObject. Actually, there is
a "feature" (I would call it a bug) in tObject - its Init method will fill
all fields with 0. Normally an object should only deal with its own data
members, not those of its descendants. I had an object once, whose
constructor called a virtual method. This is usually OK. But in one of the
types descended from this, the overridden method depended on a data member
being initialised before it was called. That type's constructor initialised
that data member before calling the inherited constructor, which called its
inherited constructor and so on. Unfortunately, the ultimate ancestor was
tObject, which cleared out all the data members. Then on the way back, my
virtual method got called, and of course went wrong. All right, so my code
wasn't the best and I was trying to be too clever, but even so, when you're
using objects for encapsulation, you wouldn't think the compiler vendor
would bypass that encapsulation.

Now: virtual methods. This is basically just calling procedures through a
pointer (like a procedural variable) stored with the object. That pointer
is initialised when the constructor is called, which is why you need a
constructor if you have virtual methods. Actually the pointer is in the
virtual method table but that's incidental. When you call a virtual method,
the compiler looks at the object, gets the pointer to the method, and calls
it. That's not very interesting when you know the type of the object. But
if you are writing a procedure, and that procedure is passed a pointer to
an object, then you _don't_ know the type. Procedure foo(poi:ptSomeObject);
could be passed the address of a tSomeObject or the address of any object
descended from that. So any virtual method call in foo _doesn't_ _know_
_what_ _it_ _is_ _doing_! - which is a really powerful technique if used
intelligently. Whoever wrote the object knows what must happen, and
presumably whatever ends up getting called will do the correct thing for
whatever object is actually being used. And another thing - the procedure
could even be a method of the object! (although the pointer is not passed
explicitly in that case). In other words:

type
  tO4 = Object
    Procedure foo; virtual;
    Procedure bar;
  End;
Procedure tO4.Foo; Begin...End;
Procedure tO4.Bar;
Begin
  foo;  
End;  

In this case, you don't know what 'foo' procedure is going to be called by
bar. If the object in use is a tO4, then it is the foo that you wrote for
tO4. But if, some years from now, someone takes your unit and creates a
descendant type

type
  tO5 = Object(tO4)
    Procedure Foo; virtual;
  End;
...
x.bar;   ... where x is an instance of tO5

then this will call the bar procedure written for tO4, but that _old_ code
(which has not been modified or even copied anywhere) will now call the
_new_ foo. Without even being recompiled. And not just bar but also all
other code written for tO4 will work with the new object, assuming that the
"meaning" of foo has remained the same. So if tO4.Foo was supposed to draw
the object on the screen, tO5.Foo should do whatever "draw the object on
the screen" translates to for a t05.

This really impressed me when it struck me - after I had written quite a
large program and modified it by extending an object. And all that old
code, literally thousands of lines, worked with the new object without me
needing to change them. Since then I've found ANSI C or TurboPascal pre-5.5
really hard.

Quote
> #3: How can I access the fields {the object in declared in another unit}
from
> another unit?

If the fields are not declared as private, then you simply access them as
if they were record fields. If they are private then they are effectively
part of the "implementation" section of the unit and you can't touch them.
Private seems to have been introduced for the case where you would like
some variables or suchlike in the implementation section. The entire object
has to be declared in one go, so unfortunately a human programmer looking
at the interface section of the unit can see the private parts, but code in
other units can't. Even descendants can't, so I don't use Private much.

Hope that's of some help, or at least intelligible.

Frank

Re:Are constructor and destructor required for EVERY object?


In article <01bce0cb$f0765100$c1947dc2@fp>,

Quote
Frank Peelo <fpe...@indigo.ie> wrote:

>Type
>  tO1:Object
>    a: integer;
>    Procedure SetA(x:integer);
>  End;
>  pr = ^r;
>  r:Record
>    a:integer;
>  End;
>Procedure tO1.SetA(x:Integer);
>Begin
>  a := x;  
>End;
>Procedure RSetA(p:pR; x:Integer);
>Begin
>  p^.a := x;
>End;

>These are really the same except that in the Record case you have to use
>the pointer explicitly.

Only if your brain has been destroyed by C-disease. Pascal has things
called variable parameters.

Procedure RSetA(rr:R; x:Integer);
Begin
  rr.a := x;
End;

Quote
>his not only clutters the code but also compiles
>to less efficient object code because the compiler _expects_ to be using
>the "self" pointer  a lot so it optimises a bit.

Same can be achieved with WITH.

Osmo

Re:Are constructor and destructor required for EVERY object?


In article <6388eq$...@kruuna.helsinki.fi>,

Quote
Osmo Ronkanen <ronka...@cc.helsinki.fi> wrote:

>Procedure RSetA(rr:R; x:Integer);
>Begin
>  rr.a := x;
>End;

That should naturally be (var rr:R;...)

Osmo

Re:Are constructor and destructor required for EVERY object?


Osmo Ronkanen <ronka...@cc.helsinki.fi> wrote in article
<6388eq$...@kruuna.Helsinki.FI>...

Quote
> >These are really the same except that in the Record case you have to use
> >the pointer explicitly.

> Only if your brain has been destroyed by C-disease. Pascal has things
> called variable parameters.

OK, I could have left off the pointer type, but it doesn't change my point.
Static methods are still pretty much the same as normal procedures in which
a reference to the instance is passed in as a pointer. And Var parameters
are the same as pointers to me. I wanted that to be clear so that the
difference between that and virtual methods was more obvious.

Quote
> >... the compiler _expects_ to be using
> >the "self" pointer  a lot so it optimises a bit.

> Same can be achieved with WITH.

Gave up using With years ago. Too confusing: if a field gets added to the
record sometime which conflicts with a local variable in the procedure
where With is used, the record's field is used in preference to the local
variable:

Type
  t = Record
    a, b : Word;
    i : Integer; { added by another programmer a year later }
  End;

Procedure x(var r:t); { in another unit somewhere }
Var
  i : Integer;
Begin
  With r do
  Begin
    i := a+b;
      { this statement changes meaning with the change in t
      }
  End;
End;

So changing t breaks x. Pascal is usually very good at either doing what I
think I was asking for or giving an error at compile time, but With is an
exception. I'd prefer to leave it out and make "i := r.a+r.b" explicit:
even if the object code is less efficient, the program is more robust when
being maintained.

Frank

Other Threads