Deleting something from an array isn’t as easy as deleting
something from a list. A list object will usually have a
Delete or Remove method to call. With an array,
you need to do everything yourself.
Since an array is always contiguous—there are never any
“holes” in it—you cannot simply remove an item
from the middle of an array. Instead, what you need to do is shift all the
elements from the latter portion of the array down by one index to fill in
the vacated position. The most obvious way to do that is with a simple
loop, as shown in Listing 1—substitute whatever type you want
for X. A more direct way is to use with the
Move procedure, in Listing
2.
After the elements have been shifted, there will still be a vacant
element at the end of the array. If the array is a static array, then
there is nothing you can do about it. Just remember that the item at the
end is not valid. You can do this if you keep track of the array’s
capacity separately from its effective size. If the array is a dynamic
array, then you can remove the vacant element at the end with the
SetLength procedure.
Listing 1
Using a loop to remove an item from an array
type
TXArray = array of X;
procedure DeleteX(var A: TXArray; const Index: Cardinal);
var
ALength: Cardinal;
i: Cardinal;
begin
ALength := Length(A);
Assert(ALength > 0);
Assert(Index < ALength);
for i := Index + 1 to ALength - 1 do
A[i - 1] := A[i];
SetLength(A, ALength - 1);
end;
Special considerations
If the array contains compiler-managed types, then the task is a little more complicated
because Move will not clean up the element you plan to
overwrite, so if that element is an AnsiString, for instance,
then the string will not have its reference count decremented, and that can
lead to memory leaks. What’s even worse is that the Move
will leave a copy of the final element at the end of the array,
which means that element will be cleaned up twice, and that can lead to
access violations and other exceptions.
The code in Listing 2 will work for any value of
X, including compiler-managed types, because it
uses the Initialize and Finalize procedures.
The Finalize call decrements reference counts and releases
memory according to the rules for compiler-managed types. The call to
SetLength will implicitly call Finalize on the
last element of the array, but that is not what we want, in this case,
because Move copied that element somewhere else without
adjusting any reference counts. To nullify SetLength’s
implicit finalization, the code calls Initialize to put the
element into a state in which finalization simply has no effect.
Listing 2
Removing an item from an array
type
TXArray = array of X;
procedure DeleteX(var A: TXArray; const Index: Cardinal);
var
ALength: Cardinal;
TailElements: Cardinal;
begin
ALength := Length(A);
Assert(ALength > 0);
Assert(Index < ALength);
Finalize(A[Index]);
TailElements := ALength - Index;
if TailElements > 0 then
Move(A[Index + 1], A[Index], SizeOf(X) * TailElements);
Initialize(A[ALength - 1]);
SetLength(A, ALength - 1);
end;
If you use the code above, but your X type is
not a compiler-managed type, then the compiler will issue hints telling you
that the Initialize and Finalize calls are not
necessary; it is safe for you to comment them out in that case.