Counting references on strings is thread-safe. Locks and exception handlers are used to safeguard the process. Consider the following code, with comments indicating where the compiler inserts code at compile time to manage reference counts:
procedure PassWithNoModifier(S: string);
// prologue: Increase reference count of S (if non-negative),
// and enter a try-finally block
begin
// Create a new string to hold the contents of S and 'X'. Assign the new string to S,
// thereby reducing the reference count of the string S originally pointed to and
// brining the reference count of the new string to 1.
// The string that S originally referred to is not modified.
S := S + 'X';
end;
// epilogue: Enter the `finally` section and decrease the reference count of S, which is
// now the new string. That count will be zero, so the new string will be freed.
procedure PassWithConst(const S: string);
var
TempStr: string;
// prologue: Clear TempStr and enter a try-finally block. No modification of the reference
// count of string referred to by S.
begin
// Compile-time error: S is const.
S := S + 'X';
// Create a new string to hold the contents of S and 'X'. TempStr gets a reference count
// of 1, and reference count of S remains unchanged.
TempStr := S + 'X';
end;
// epilogue: Enter the `finally` section and decrease the reference count of TempStr,
// freeing TempStr because its reference count will be zero.
As shown above, introducing temporary local string to hold the modifications to a parameter involves the same overhead as making modifications directly to that parameter. Declaring a string const
only avoids reference counting when the string parameter is truly read-only. However, to avoid leaking implementation details outside a function, it is advisable to always use one of const
, var
, or out
on string parameter.