Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

const in parameters

3 views
Skip to first unread message

Developer Express - Jimmy Tharpe

unread,
Jul 12, 2002, 1:24:06 AM7/12/02
to
[followup set: borland.public.delphi.objectpascal]

> Is there a difference between the following procedure definitions?
>
> procedure Test(Value: TObject)

This passes a copy of the pointer pointing to the object instance. The
"Value" pointer can be modified, but the modification is only within the
scope of the "Test" procedure. This, for example:

procedure Test(Value: TObject)
begin
ShowMessage(Value.ClassName);
end;

Is logically the same thing as this:

procedure Test(const Value: TObject)
var
ValueCopy : TObject;
begin
ValueCopy := Value;
ShowMessage(ValueCopy.ClassName);
end;

Obviously any change made to "ValueCopy" will not affect the value of
"Value".

> procedure Test(var Value: TObject)

This passes the Value pointer and allowes you to modify it. Consider this
example:

procedure Test(var Value: TObject)
begin
Value := nil;
end;

... { elsewhere in code } ..

var
B : TComponent;
begin
B := TButton.Create(nil);
B.Caption := 'Hello World!';
ShowMessage(B.Caption);
Test(B);
ShowMessage(B.Caption); // Uh oh! B is nil!
end;

In this example, B is suddenly polinting to nil after you call Test(B)
because it was passed as a reference and that reference was modified. This,
BTW, is also a memory leak because B never gets freed ;).

> procedure Test(const Value: TObject)

This is similar to var, except you cannot modify "Value".

procedure Test(const Value: TObject)
begin
Value := nil; // Compile error!
end;

--
-Jimmy
Developer Express:
http://www.devexpress.com/
Used-Disks:
http://www.used-disks.com/
:


Uffe Kousgaard

unread,
Jul 12, 2002, 5:35:56 AM7/12/02
to
"Developer Express - Jimmy Tharpe"
<ji...@I.do.not.like.SPAM.devexpress.com.com> wrote in message
news:3d2e679d_1@dnews...

> procedure Test(Value: TObject)
> begin
> ShowMessage(Value.ClassName);
> end;
>
> Is logically the same thing as this:
>
> procedure Test(const Value: TObject)
> var
> ValueCopy : TObject;
> begin
> ValueCopy := Value;
> ShowMessage(ValueCopy.ClassName);
> end;
>
> Obviously any change made to "ValueCopy" will not affect the value of
> "Value".

Not quite right, this changes the tag of Value, since ValueCopy points
to the same object as Value:

procedure Test(const Value: TComponent)
var
ValueCopy : TComponent;
begin
ValueCopy := Value;
ValueCopy.Tag:= ValueCopy.Tag+1;
end;


This doesn't change anything:

procedure Test(const Value: integer)
var
ValueCopy : integer;
begin
ValueCopy := Value;
ValueCopy:= ValueCopy+1;
end;


Developer Express - Jimmy Tharpe

unread,
Jul 12, 2002, 12:45:59 PM7/12/02
to
> > Obviously any change made to "ValueCopy" will not affect the value of
> > "Value".
>
> Not quite right, this changes the tag of Value, since ValueCopy points
> to the same object as Value:

No, it's right ;). You're example is not modifying "Value" instead it's
modifying the object "Value" points to.

> procedure Test(const Value: TComponent)
> var
> ValueCopy : TComponent;
> begin
> ValueCopy := Value;
> ValueCopy.Tag:= ValueCopy.Tag+1;
> end;

--

Jay Huber

unread,
Jul 12, 2002, 5:09:29 PM7/12/02
to
"Developer Express - Jimmy Tharpe"
<ji...@I.do.not.like.SPAM.devexpress.com.com> wrote in message
news:3d2e679d_1@dnews...
> [followup set: borland.public.delphi.objectpascal]

>
> > procedure Test(const Value: TObject)
>
> This is similar to var, except you cannot modify "Value".

Actually, there are numerous other ways in which "const" and "var" differ.
Specifically,

1) "var" is the only one of the three that requires the actual and formal
types to match -- you can pass a TButton to the other two routines, but not
to the one that uses "var". The one that uses "var" can only accept a
TObject variable, while the other two may accept any type of object.

2) "var" requires a variable (i.e., an L-value), while the other two don't.
For example, "Test(TObject.Create)" doesn't work with "var", but does work
with the other two.

3) "var" is the only one of the three that always employs an extra level of
indirection -- "var" passes the address of the TObject pointer, while
"const" and pass-by-value pass the TObject pointer directly. This is not
true of all types, but it is true of TObject. In other cases, a record or
array for example, "const" is more like "var" in that neither one copies the
value. It may be viewed as a "behind-the-scenes optimization" that "const"
doesn't add an extra level of indirection to TObject arguments.
Nevertheless, "procedure P(X: TObject)" and "procedure P(const X: TObject)"
generate the same machine code at the point of call as well as the routine P
itself, while "procedure P(var X: TObject)" generates different code at the
point of call and the routine itself.

So, here is my summary of the differences and similarities...

pass-by-value
-passes a copy
-allows modifications to the formal argument, but these changes do not
affect the actual argument
-does not require an L-value
-does not require the actual argument to be the exact same type as the
formal argument
-does not clear the actual argument at the point of call
-does not suffer from the "const interface" bug (*)

pass-by-const
-sometimes passes a copy, sometimes passes a reference to the actual
argument (depending on the data type)
-does not allow modifications to the formal argument
-does not require an L-value
-does not require the actual argument to be the exact same type as the
formal argument
-does not clear the actual argument at the point of call
-suffers from the "const interface" bug (*)

pass-by-reference (aka "var")
-always passes a reference to the actual argument,
-allows modifications to the formal argument, and these changes do affect
the actual argument
-requires the actual argument to be an L-value
-requires the actual argument to be the exact same type as the formal
argument
-does not clear the actual argument at the point of call
-does not suffer from the "const interface" bug (*)

And let's not forget the "out" keyword...

pass-by-output (aka "out")
-always passes a reference to the actual argument,
-allows modifications to the formal argument, and these changes do affect
the actual argument
-requires the actual argument to be an L-value
-requires the actual argument to be the exact same type as the formal
argument
-clears the actual argument at the point of call (for managed types, for
example, string, interface, dynamic array, variant, etc.)
-does not suffer from the "const interface" bug (*)

(*) What is the "const interface" bug? It is a well-known issue with the
"const" calling convention when used with reference-counted data types
(string, interface, dynamic array, etc.). The issue is this: at the point
of call, the compiler does the same thing it does in pass-by-value (it
passes a copy of the reference to the called routine), but does not add to
the reference count (even though it is creating one more reference). For
example,

var
Q: string;

procedure Test(const S: string);
begin
Q := '';
ShowMessage(S);
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
Q := 'hello';
Test(Q);
end;

In the above program, the call to ShowMessage generates an access violation.
The reason is simple: setting Q to the empty string releases the memory that
Q points to -- unfortunately, S points to the same memory. In other words,
we have two references (Q and S) to the string, but the string has a
reference count of 1. When we release one reference (Q), the reference
count goes to zero and the string is destroyed, leaving the other reference
(S) to "dangle". A simliar problem occurs with interfaces and dynamic
arrays, which are also reference-counted. Furthermore the problem also
occurs when the "const" parameter is a record or static array that contains
one of these reference-counted types (but only if the record or static array
is small enough for "const" to pass a copy rather than a reference -- in
Delphi 6, this means 4 bytes).

The solution is for the compiler, at the point of call, to employ a
"temporary" to ensure that an extra reference is added to the value before
the call (and subsequently released sometime after the call).


-Jay Huber
JayH...@Dimeric.com

Uffe Kousgaard

unread,
Jul 12, 2002, 5:28:52 PM7/12/02
to
"Developer Express - Jimmy Tharpe"
<ji...@I.do.not.like.SPAM.devexpress.com.com> wrote in message
news:3d2f076c$1_1@dnews...

> > > Obviously any change made to "ValueCopy" will not affect the value
of
> > > "Value".
> >
> > Not quite right, this changes the tag of Value, since ValueCopy
points
> > to the same object as Value:
>
> No, it's right ;). You're example is not modifying "Value" instead
it's
> modifying the object "Value" points to.

Sure, but the inexperienced reader may not note that difference. He/she
thinks it is safe to change ValueCopy and whatever it points to. From
Ray's question, I would not expect him to know that.

Developer Express - Jimmy Tharpe

unread,
Jul 12, 2002, 9:14:03 PM7/12/02
to
> Sure, but the inexperienced reader may not note that difference. He/she
> thinks it is safe to change ValueCopy and whatever it points to. From
> Ray's question, I would not expect him to know that.

I agree. I hope it's clear now :).

Ed Dressel

unread,
Jul 15, 2002, 4:53:03 PM7/15/02
to
> procedure Test(const Value: TObject)
> var
> ValueCopy : TObject;
> begin
> ValueCopy := Value;
> ShowMessage(ValueCopy.ClassName);
> end;
>
> Obviously any change made to "ValueCopy" will not affect the value of
> "Value".

This is *not* true. ValueCopy still points to the value object.

Ed Dressel


Developer Express - Jimmy Tharpe

unread,
Jul 22, 2002, 4:53:19 PM7/22/02
to
> > Obviously any change made to "ValueCopy" will not affect the value of
> > "Value".
>
> This is *not* true. ValueCopy still points to the value object.

Ed - see my response to Uffe. It is true, I just did not make a distinction
between the object and the object reference.

0 new messages