Function and Method Arguments
Passing Arguments by Value
Objects vs. Primitive types in Java: In Java, the rules for what is stored in a variable are simple: all object variables store object references while primitive type variables store values directly. In Java, assignment is done by value, so when you assign a primitive variable’s value to another variable, the actual value is copied; when it is an object variable, the reference is copied, and you thus get two references to the same object.
Objects and Primitive types in C++: In C++, on the other hand, there is no such distinction between object and primitive type variables. By default, all variables, both primitives and objects, actually hold values, and NOT object references. C++, like Java, does assignment by value. However, this means that when one object variable is assigned to another, a copy of the entire object is made (like calling clone in Java). This behaviour is seen as well when one calls a method with parameters. The parameters are assigned by value, hence copied. This behavior is illustrated in the code example.
#include <iostream>
#include <string>
void addOne(int i) {
// Has no effect outside this scope because it's a copy from the original
i++;
}
void changeFirst(std::string s) {
// Has no effect outside this scope because it's a copy from the original
s[0] = '#';
}
int main() {
int i = 0;
addOne(i);
std::cout << "Value of i is " << i << std::endl;
std::string s("Hello");
changeFirst(s);
std::cout << "Value of s is " << s << std::endl;
return 0;
}
To avoid this copying-the-data behaviour in C++, one can use references or pointers.
Passing Arguments by Pointer
Pointers are a low-level mechanism for referencing memory locations. When passing a pointer as an argument, you pass the memory address of an object, allowing the function to modify the original object.
Pointers in C++ are actually quite similar to references in Java. They are variables that store the memory address of some data, as opposed to storing the data directly. That is, their value is a memory address. A major difference is that in C++, a pointer merely stores the address of the object and it does contain any mechanism for counting references to the object.
If T is some type, then T* is the syntax to declare a pointer to a T variable. A pointer variable can be initialized either with std::nullptr, with the value of another pointer variable, with the memory address of another variable, or with a call to new. The memory address of a variable can be attained by placing the & symbol before the variable name. To access the object to which a pointer points to, you must “dereference” the pointer by placing the * symbol before the variable name. To access an instance variable of the object to which a pointer points, you can use the -> operator on the pointer. These different scenarios are illustrated in the code example.
#include <iostream>
#include <string>
void addOne(int *i) {
// Actually changes the original variable, but i can be null !
(*i)++;
}
void changeFirst(std::string *s) {
// Actually changes the original variable
s->at(0) = '#';
}
int main() {
int i = 0;
addOne(&i);
std::cout << "Value of i is " << i << std::endl;
std::string s("Hello");
changeFirst(&s);
std::cout << "Value of s is " << s << std::endl;
return 0;
}
Passing Arguments by Reference
C++ references are NOT the same as Java references. Although they are related, how they are used and their syntax is pretty different, so it is best if you simply think of them as different concepts.
References in C++ are simply aliases for existing variables. When you define a reference variable in C++, the variable is treated exactly as another name as the variable you set it to. Thus, when you modify the reference variable, you modify the original variable as well without needing to do anything special.
The syntax for creating a reference variable in C++ is to place an & after the type name in the variable declaration. If T is some type, then T& is the syntax to declare a reference to a T variable.
#include <iostream>
#include <string>
void addOne(int &i) {
// Actually changes the original variable
i++;
}
// Function overloading doesn't work here, so we have to change the name
void addOne_val(int i) {
// Has no effect outside this scope because this is a copy of the original
i++;
}
void changeFirst(std::string &s) {
// Actually changes the original variable
s[0] = '#';
}
int main() {
int i = 0;
addOne(i);
std::cout << "Value of i is " << i << std::endl;
std::string s("Hello");
changeFirst(s);
std::cout << "Value of s is " << s << std::endl;
return 0;
}
When to Use References
There are two main uses for C++ references: parameter passing and aliasing long variable names. In many cases, it’s extremely useful not to make copies of objects when they are passed to functions, either because you want the function to be able to modify the data in the object, or because you want to avoid wasting time and space with the copy.
Use the const keyword to prevent modification of the object passed by reference. This is a good practice to avoid unintended side effects and to make it clear that the function does not modify the input.
Overloading Note
Note: Function or method overloading is possible in C++. Overloading means that several methods or functions can have the same name but have different parameters (order or types). It is also important to note that since a method with the same parameter type passed by value or reference is invoked in the same way, it is not possible to overload functions or methods in this case. This is the reason why the function addOne() that takes one int parameter as a value must have a different name (e.g. addOne_val() here) as the addOne() function that takes one int parameter as reference.