Another feature related to overloading, is the possibility of giving a default value to some of the parameters of a function, so that you can call the function with or with-out those parameters. If the parameter is missing in the call, it will take the default value.
Let me show an example (still part of the OverloadTest application project). We can define the following encapsulation of the Show call, providing two default parame-ters:
procedure NewMessage (Msg: string;
Caption: string = 'Message';
Separator: string = ': ');
begin
Show (Caption + Separator + Msg);
end;
With this definition, we can call the procedure in each of the following ways:
NewMessage ('Something wrong here!');
NewMessage ('Something wrong here!', 'Attention');
NewMessage ('Hello', 'Message', '--');
This is the output:
Message: Something wrong here!
Attention: Something wrong here!
Message--Hello
Notice that the compiler doesn't generate any special code to support default param-eters; nor does it create multiple (overloaded) copies of the functions or procedure.
The missing parameters are simply added by the compiler to the calling code. There is one important restriction affecting the use of default parameters: You cannot
"skip" parameters. For example, you can't pass the third parameter to the function after omitting the second one.
There are a few other rules for the definition and the calls of functions and proce-dures (and methods) with default parameters:
• In a call, you can only omit parameters starting from the last one. In other words, if you omit a parameter you must also omit the following ones.
• In a definition, parameters with default values must be at the end of the parame-ters list.
• Default values must be constants. Obviously, this limits the types you can use with default parameters. For example, a dynamic array or an interface type can-not have a default parameter other than nil; records cannot be used at all.
Marco Cantù, Object Pascal Handbook
• Parameters with defaults must be passed by value or as const. A reference (var) parameter cannot have a default value.
Using default parameters and overloading at the same time makes it more likely to get you in a situation which confuses the compiler, raising an ambiguous call error, as mentioned in the previous section. For example, if I add the following new ver-sion of the NewMessage procedure to the previous example:
procedure NewMessage (Str: string; I: Integer = 0); overload;
begin
Show (Str + ': ' + IntToStr (I)) end;
then the compiler won't complain, as this is a legitimate definition. However, the call:
NewMessage ('Hello');
is flagged by the compiler as:
[dcc32 Error] E2251 Ambiguous overloaded call to 'NewMessage' Related method: procedure NewMessage(string; string; string);
Related method: procedure NewMessage(string; Integer);
Notice that this error shows up in a line of code that compiled correctly before the new overloaded definition. In practice, we have no way to call the NewMessage proce-dure with one string parameter, as the compiler doesn't know whether we want to call the version with only the string parameter or the one with the string parameter and the integer parameter with a default value. When it has a similar doubt, the compiler stops and asks the programmer to state his or her intentions more clearly.
Inlining
Inlining Object Pascal functions and methods is a low-level language feature that can lead to significant optimizations. Generally, when you call a method, the com-piler generates some code to let your program jump to a new execution point. This implies setting up a stack frame and doing a few more operations and might require a dozen or so machine instructions. However, the method you execute might be very short, possibly even an access method that simply sets or returns some private field.
In such a case, it makes a lot of sense to copy the actual code to the call location, avoiding the stack frame setup and everything else. By removing this overhead, your program will run faster, particularly when the call takes place in a tight loop exe-cuted thousands of times.
Marco Cantù, Object Pascal Handbook
For some very small functions, the resulting code might even be smaller, as the code pasted in place might be smaller than the code required for the function call. How-ever, notice that if a longer function is inlined and this function is called in many different places in your program, you might experience code bloat, which is an unnecessary increase in the size of the executable file.
In Object Pascal you can ask the compiler to inline a function (or a method) with the inline directive, placed after the function (or method) declaration. It is not neces-sary to repeat this directive in the definition. Always keep in mind that the inline directive is only a hint to the compiler, which can decide that the function is not a good candidate for inlining and skip your request (without warning you in any way).
The compiler might also inline some, but not necessarily all, of the calls of the func-tion after analyzing the calling code and depending on the status of the $INLINE directive at the calling location. This directive can assume three different values (notice that this feature is independent from the optimization compiler switch):
● With {$INLINE OFF} you can suppress inlining in a program, in a portion of a program, or for a specific call site, regardless of the presence of the inline directive in the functions being called.
● With default value, {$INLINE ON}, inlining is enabled for functions marked by the inline directive.
● With {$INLINE AUTO} the compiler will generally inline the functions you mark with the directive, plus automatically inline very short functions.
Watch out because this directive can cause code bloat.
There are many functions in the Object Pascal Run-Time Library that have been marked as inline candidates. For example, the Max function of the System.Math unit has definitions like:
function Max(const A, B: Integer): Integer;
overload; inline;
To test the actual effect of inlining this function, I’ve written the following loop in the InliningTest application project:
var
sw: TStopWatch;
I, J: Integer;
begin J := 0;
sw := TStopWatch.StartNew;
for I := 0 to LoopCount do J := Max (I, J);
sw.Stop;
Show ('Max ' + J.ToString +
' [' + sw.ElapsedMilliseconds.ToString + '] ');
Marco Cantù, Object Pascal Handbook
In this code, the TStopWatch record of the System.Diagnostics unit, a structure that keep track of the time (or system ticks) elapsed between the Start (or StartNew) and the Stop calls.
The form has two buttons both calling this same exact code, but one of them has inlining disabled at the call site. Notice you need to compile with the Release config-uration to see any difference (as inlining is a Release optimization). With twenty million interactions (the value of the LoopCount constant), on my computer I get the following results:
// on Windows (running in a VM) Max on 20000000 [17]
Max off 20000000 [45]
// on Android (on device) Max on 20000000 [280]
Max off 20000000 [376]
How can we read this data? On Windows, inlining more than doubles the execution speed, while on Android it makes the program about 35% faster. However, on a device the program runs much slower (an order of magnitude) so while on Windows we shave off 30 milliseconds on my Android device this optimization saves about 100 milliseconds.
The same program does a second similar test with the Length function, a compiler-magic function that was specifically modified to be inlined. Again the inlined version is significantly faster on both Windows and Android:
// on Windows (running in a VM) Length inlined 260000013 [11]
Length not inlined 260000013 [40]
// on Android (on device) Length inlined 260000013 [401]
Length not inlined 260000013 [474]
This is the code used by this second testing loop:
var
sample:= 'sample string';
sw := TStopWatch.StartNew;
for I := 0 to LoopCount do
Marco Cantù, Object Pascal Handbook
The Object Pascal compiler doesn’t define a clear cut limit on the size of a function that can be inlined or a specific list of constructs (for or while loops, conditional statements) that would prevent inlining. However, since inlining a large function provides little advantage yet exposes you to the risk of some real disadvantages (in terms of code bloat), you should avoid it.
One limitation is that the method or function cannot reference identifiers (such as types, global variables, or functions) defined in the implementation section of the unit, as they won’t be accessible in the call location. However, if you are calling a local function, which happens to be inlined as well, then the compiler will accept your request to inline your routine.
A drawback is that inlining requires more frequent recompilations of units, as when you modify an inlined function, the code of each of the calling sites will need to be recompiled as well. Within a unit, you might write the code of the inlined functions before calling them, but better place them at the beginning of the implementation section.
note Object Pascal uses a single pass compiler, so it cannot paste in the code of a function it hasn’t compiled yet.
Within different units, you need to specifically add other units with inlined functions to your uses statements, even if you don’t call those methods directly. Suppose your unit A calls an inlined function defined in unit B. If this function in turn calls another inlined function in unit C, your unit A needs to refer to C as well. If not, you’ll see a compiler warning indicating the call was not inlined due to the missing unit reference. A related effect is that functions are never inlined when there are cir-cular unit references (through their implementation sections).