Archive
Passing arguments to a function: by-value, by-reference, by-object…
When calling a function, the exact mechanism for passing arguments (assigning arguments to parameters) depends on the evaluation strategy and how it is implemented. Some common ways of passing arguments to a function are:
- by value
- by reference
- by object
This is not a theoretical article about passing arguments and doesn’t contain rigorous definitions. I just will try to show clearly how the above mechanisms work on some specific languages widely used: C, C++, Python and Perl.
C
In C, a variable declaration doesn’t create anything, it just ensures that, at runtime, there will be a chunk of memory in the stack big enough to hold the variable value. Keeping that in mind let’s look at the following simple code:
int a = 3;
void update_the_copy(int b) {b = 5;}
update_the_copy(a);
a == 5; // Evaluates to false
In the above example we have two variable declarations, one for a
and one more for b
. They point to different chunks of memory. When the function is called its arguments are evaluated and a copy of their value is assigned to the function parameters (so b
contains a byte-to-byte copy of a
). This is called to pass by value. When doing it there is no way to change the arguments from inside the function simply because one has no access to them. The only way to return a value to the caller is via the function’s return value.
Obviously that is memory inefficient when working with large values or nontrivial types. And it doesn’t allow to change in-place variables defined in the caller. C solves the problem by passing (by value) a pointer. In this case the passed value is a memory address i.e.; just a few bytes, so passing the argument is cheap in terms of memory. Although the original pointer and the passed pointer are different entities, they still point to the same memory address so the function can change the pointed data in-place:
int a = 3;
int *pa = &a;
void update_inplace(int *b) {*b = 5;}
update_inplace(pa);
a == 5; // Evaluates to true
C++
C++ supports passing-by-value. However it doesn’t use pointers to deal with the problems of passing a large amount of data or changing values in-place. Instead it uses references. A C++ reference allows you to create a new name for a variable. The reference doesn’t contain a copy of the variable but behind the scenes it keeps around its address. So you can use the reference to read or change the original value stored in that variable:
void swap_values(int& a, int& b) {
temp = a;
a = b;
b = temp;
}
int first = 1;
int second = 2;
swap_values(first, second);
After the swap first
will be 2 and second
will be 1. Notice that when we call the function we use regular int
variables, not references. But, in the function definition, the parameters are references. As a result when the function is called it receives an implicit reference to the variables used as arguments instead of a copy of them. This is called to pass by reference. It allows the function to access the original arguments, no a copy of them. And changes to those values inside the function will be seen outside the function.
Perl
Perl always passes by reference, but not in the same form that C++. The Perl syntax for defining functions doesn’t include a explicit signature so the programmer cannot declare a given parameter as being a reference as she does in C++. Perl passes by reference using an aliasing mechanism: when a function is called, all incoming arguments are flattened into a single list which is then aliased to the implicit variable @_. In this way the function gets implicit references to the arguments:
sub swap_values
{
my $temp = $_[0]; # Make a local copy of the first received argument
$_[0] = $_[1];
$_[1] = $temp;
}
my $first = 1;
my $second = 2;
swap_values($first, $second);
After the swap first
will be 2 and second
will be 1. Once again, notice that passing by reference doesn’t mean that we use Perl references as arguments, Perl references are another mechanism provided by Perl for modifying values in-place. The important thing is that when the function is called it receives implicit references to the arguments. Therefore we are passing by reference. Also notice that the flattening of arguments can be tricky (although this quirk has nothing to do with the fact that Perl passes by reference. If you are interested, you can find nice details about flattening here ).
To the end to simulate pass-by-value one needs to do explicit copies of the arguments inside the function:
my $name = "Chuck";
sub reverse_name
{
my $name = shift; # a local copy of the argument
$name = reverse $name; # change the local copy
}
reverse_name($name);
print $name; # output "Chuck"
Python
Things are different in Python. All Python values are objects (so they are first-class entities) that can be tagged with one or more names or can have no name at all. Variable names are just names (you can see it graphically here). As a result talking about pass-by-value or pass-by-reference makes non sense in Python (unless one consider that CPython is implemented in C. But I will stay at Python level in this section).
So how does Python pass arguments to a function? It passes by object. Let’s see what does it means with a couple of examples::
obj1 = 3
def simulate_pass_by_value(obj2):
obj2 = 5
simulate_pass_by_value(obj1)
obj1 == 5 # Evaluates to false
We get the same result as passing by value. It is not an unexpected result if one remember what we have said about object names at the beginning of this section. The object named obj1
is passed itself to the function. When doing so a new tag, obj2
, is added to it. Now the object has two names, obj1
and obj2
. The effect of the assignment inside the function is to move the obj2
name to a new object whose value is 5. When the function ends the object named obj2
goes away and the passed object remains untouched and can still be accessed with the obj1
name.
In the following example we pass a mutable object to a function in order to simulate a pass by reference:
def simulate_pass_by_reference(list2):
list2.append(2)
list1 = [1]
simulate_pass_by_reference(list1)
list1 # Evaluates to [1, 2]
Now we get the same result as passing by reference i.e; the argument is changed in-place. The object named list1
is passed to the function and a new tag, list2
, is attached to it. In this case the new name is never reassigned to a new value. Instead, a method call modifies the object in-place. When the function ends the list2
tag goes away but the modified object is still accessible via its list1
tag.
In summary
- C always passes by-value. Functions can use pointer arguments to change caller variables in-place.
- C++ supports both, pass-by-value and pass-by-reference.
- Perl always passes by-reference. Functions can do local copies of its arguments in order to simulate passing by-value.
- Python passes by-object. Functions can simulate passing by-value and passing by-reference.
QHeaderView sections: visualIndex vs logicalIndex
Headers provided by QHeaderView are made of sections which can be referred to by using indexes. There are two kind of indexes, visual and logical. I think the reference documentation is somewhat confusing when explaining what those indexes are and how they differ from each other. This blog entry is the result of trying to clarify those issues to myself.
Visual indexes tell us the current position that a given section occupies in the header. So let’s suposse we create a five columns table and that we label the sections of its horizontal header as C0, C1, C2, C3 and C4 (the C being a shorthand for column). The visual indexes of these sections are showed in the next figure:
C0 | C1 | C2 | C3 | C4 | |
---|---|---|---|---|---|
Visual Index | 0 | 1 | 2 | 3 | 4 |
By definition, the visual index of a given section will change if the section is moved to a different position. For example, if we move the C3 section to the second position of the header (via QHeaderView.moveSection(3, 1)) then the position of sections C1, C2 and C3 will change. As a result also their visual indexes will change:
C0 | C3 | C1 | C2 | C4 | |
---|---|---|---|---|---|
Visual Index | 0 | 1 | 2 | 3 | 4 |
As we can see even after moving around some sections the set of visual indexes remains ordered as 0, 1, 2… N. So far so good.
However there is a detail one has to be aware of: hiding/showing a given section (calling QHeaderView.hideSection method) does NOT change its visual index as one could expect. So if in our current header we hide the section C0 no visual index will change:
C3 | C1 | C2 | C4 | |
---|---|---|---|---|
Visual Index | 1 | 2 | 3 | 4 |
Time to talk about logical indexes. For a given section the value of its logical index depends on the position at which it was added to the header. So when our sample table was just created we had:
C0 | C1 | C2 | C3 | C4 | |
---|---|---|---|---|---|
Logical Index | 0 | 1 | 2 | 3 | 4 |
because C0 was added at position 0, C1 was added at position 1 and so on. Moving sections around doesn’t change any logical index so if we move the C3 section to the second position we will have:
C0 | C3 | C1 | C2 | C4 | |
---|---|---|---|---|---|
Logical Index | 0 | 3 | 1 | 2 | 4 |
But inserting/removing sections (which can be done inserting/removing columns in our table model) will change the logical indexes of the implied sections. For instance, if now we insert a new column, labeled as NC for the sake of clarity, at position 2 we will have:
C0 | C3 | NC | C1 | C2 | C4 | |
---|---|---|---|---|---|---|
Logical Index | 0 | 4 | 2 | 1 | 3 | 5 |
The sections C0 and C1 had a logical index lower than 2 so they will keep their logical index unchanged but sections C2, C3 and C4 had a logical index greater than or equal to the logical index of the new section. As a result their logical index will be increased by one.
Hopefully you have now a better understanding of what visual and logical indexes are and will be able to use them without being puzzled by methods like logicalIndex, logicalIndexAt, visualIndex and visualIndexAt provided by QHeaderView.
C loops: for() and the comma operator
Everybody knows that in C
a for
loop has the syntax:
for (variable initialization; exit condition; variable update) {
// the body comes here
}
a typical example being:
int i;
for (i = 0; i < 5; i++) {
printf("%i ", i);
}
However, all three expressions in the for
statement are optional. Even the body is optional. Due to this flexibility when one starts to read C
code written by other people it is easy to find for
loops with very different looks. In general, those variations are easy to understand. For instance, the above loop can also be written as follows:
int i = 0;
for(; i < 5; i++) {
printf("%i ", i);
}
so the variable initialization has been done before the loop is called. If the exit condition is absent then it always evaluates to True. And if the update variable expression is omitted then it is done in the loop body. If all three expressions have been removed from the for
statement then we have a potentially infinite loop and we should insert a break
statement somewhere in the body.
Things get interesting when the for
statement is combined with the infrequently used comma operator.
The comma operator is a sequence point (as they are && and ||) so the order of evaluation of the operands is fixed. It is a binary operator that evaluates its first operand, performs all side effects and discards the result, and then evaluates the second operand and returns its value and type so:
x = (y, z);
will do y
and, after performing all side effects, will discard it, then do z
and finally will set x
to z
.
Because the comma operator discards its first operand, it is useful where the first operand has desirable side effects, such as in the initializer or the counting expression of a for loop.
We can use that operator to combine the variable update and the exit condition expressions of the for
statement in a single expression:
int i=-1;
for (;i=i+1,i<5;) {
printf("%i ", i);
}
As a result, at each iteration the counter is incremented and discarded, then exit condition is evaluated and its result returned so the value of the comma expression is the value of the exit condition.
The equivalent while
loop is:
int i=-1;
i = i + 1;
while(i < 5) {
printf("%i ", i);
i = i + 1;
}
In the following example the exit condition works as in the previous one. In addition, two variables are set in the variable initialization expression. And the update variable expression is used to append statements to the body of the loop:
int i, j;
for (i=-1,j=0;i=i+1,i<5;j=i+1,printf("%i\n", j)){
printf("%i\t", i);
}
It is equivalent to the following while
loop:
int i = -1;
int j = 0;
i = i + 1;
while(i<5) {
printf("%i\t", i);
j = i + 1;
printf("%i\n", j);
i = i + 1;
}
Notice that the examples above are intentionally simple. They just pretend to show you how the combination for
statement + comma operator works. In general, comma operator is used to produce side effects in the following situations:
- calling a function
- entering or repeating an iteration loop
- testing a condition
You can see also:
- this SO thread about the use of comma operator with
for
loops - a brief explanation of comma expressions with a nice summary