> 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/
:
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;
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;
--
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
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 :).
This is *not* true. ValueCopy still points to the value object.
Ed Dressel
Ed - see my response to Uffe. It is true, I just did not make a distinction
between the object and the object reference.