Show Lecture.Functions as a slide show.
CS253 Functions
Functions
By & large, C++ functions look like Java functions:
int sum(int a, int b) {
return a+b;
}
int main() {
cout << sum(2, 40) << '\n';
}
42
Default Parameters
Functions can have default trailing arguments:
int sum(int a, int b = 1) {
return a+b;
}
int main() {
cout << sum(2, 40) << '\n'
<< sum(8) << '\n';
}
42
9
Default Parameters
All default arguments must be piled up at the end:
int sum(int a = 123, int b = 456) {
return a+b;
}
int main() {
cout << sum(2, 40) << '\n'
<< sum(8) << '\n'
<< sum() << '\n';
}
42
464
579
Function Overloading
Like Java, C++ can have multiple versions
of the same function that take different arguments:
int sum(int a, int b) { return a+b; }
int sum(int a) { return a+456; }
int sum() { return 123+456; }
int main() {
cout << sum(2, 40) << '\n'
<< sum(8) << '\n'
<< sum() << '\n';
}
42
464
579
Function Overloading
The types don’t have to be the same, either:
int sum(int a, int b) { return a+b; }
double sum(int a) { return a+4.5; }
string sum(double) { return "I can’t deal with double"; }
const char *sum() { return "You are a bonehead"; }
int main() {
cout << sum(2, 40) << '\n'
<< sum(8) << '\n'
<< sum(123.456) << '\n'
<< sum() << '\n';
}
42
12.5
I can’t deal with double
You are a bonehead
Notice how, for sum(double)
, the unused argument isn’t given a name,
to avoid warnings about unused variables.
Implementation
- So, how is this accomplished?
- How can there actually be four versions of the function
sum
in the executable file?
sum
is sum
, right?
- No.
Name Mangling
- It’s called name mangling.
- Inside the executable file, the function isn’t really called
sum
.
- Instead, it’s called
sum-that-takes-an-int-argument
.
- Perhaps it’s more compact than that.
Return type doesn’t matter
- The function’s return type is not part of the mangled name.
- Therefore, a function can’t be overloaded on return type alone.
int foo() { return 1; }
double foo() { return 2.0; }
int main() {
return 0;
}
c.cc:2: error: ambiguating new declaration of 'double foo()'
Name Mangling
$ cat ~cs253/Example/overload.cc
int sum(int a, int b) { return a+b; }
double sum(int a) { return a+4.5; }
const char *sum() { return "You are a bonehead"; }
bool sum(double, int, short) { return false; }
$ g++ -c ~cs253/Example/overload.cc
$ nm --numeric-sort overload.o
0000000000000000 T _Z3sumii
0000000000000014 T _Z3sumi
000000000000002e T _Z3sumv
0000000000000039 T _Z3sumdis
$ nm --numeric-sort --demangle overload.o
0000000000000000 T sum(int, int)
0000000000000014 T sum(int)
000000000000002e T sum()
0000000000000039 T sum(double, int, short)
More Name Mangling
$ cat ~cs253/Example/mangle.cc
void b(unsigned __int128,
__int128,
long double,
unsigned char,
long double,
signed char,
double) {
}
$ g++ -c ~cs253/Example/mangle.cc
$ nm mangle.o
0000000000000000 T _Z1bonehead
The return type can be declared as auto, which means that the actual type
will be deduced by what is returned. auto is not a type.
It means “Compiler, you figure out the real type.”
auto pi() {
return 3.14159;
}
auto fact() {
cout << "π ≈ " << pi() << '\n';
}
int main() {
fact();
return 0;
}
π ≈ 3.14159
Caution
The auto type deduction tolerates no ambiguity.
auto foo(bool b) {
constexpr short default_value = 0;
if (!b)
return default_value;
else
return 42;
}
int main() {
return foo(false);
}
c.cc:6: error: inconsistent deduction for auto return type: 'short int' and
then 'int'
Pass by value
By default, arguments are passed by value.
void foo(int n) {
++n; ++n;
}
int main() {
int v = 42;
foo(v);
cout << v << '\n';
}
42
The variable n
gets a copy of v
.
Pass by address
A C programmer might pass by address:
void foo(int *n) {
*n = 99;
}
int main() {
int v = 42;
foo(&v);
cout << v << '\n';
}
99
Note that the actual parameter is &v
, the address of v
.
This is rarely the best solution. Use a reference, instead.
Pass by reference
C++ can pass a variable by reference:
void foo(int &n) {
++n; ++n;
}
int main() {
int v = 42;
foo(v);
cout << v << '\n';
}
44
The variable n
is an alias of v
. This is often implemented
as a pointer that gets *
applied automatically.
Pass by constant address
You can use a read-only pointer:
void foo(const int *n) {
*n = 99;
}
int main() {
int v = 42;
foo(&v);
cout << v << '\n';
}
c.cc:2: error: assignment of read-only location '* n'
Pass by reference
You can use a read-only reference:
void foo(const int &n) {
++n; ++n;
}
int main() {
int v = 42;
foo(v);
cout << v << '\n';
}
c.cc:2: error: increment of read-only reference 'n'
Efficiency
- When dealing with an integer, a floating-point value, or a boolean,
all the above techniques are equally fast.
- A scalar fits into a register, or gets easily pushed onto the stack.
- An object, however, is generally bigger, and might even contain
pointers to dynamic data (vector, string).
- Therefore, passing an object by value tends to be expensive,
whereas passing an object by address or reference is cheap.
- It’s all about making a copy. Is that fast, for your datatype?
Efficiency of pass by value
bool foo(vector<int> v) { // pass by value
return v.empty();
}
int main() {
vector<int> w(2500);
for (int i=0; i<10'000'000; i++)
foo(w);
cout << "Done.\n";
}
Done.
Real time: 1.94 seconds
We made ten million copies of a ten-thousand-byte vector,
and so copied 100 gigabytes. Slow! 🐌
Efficiency of pass by constant reference
bool foo(const vector<int> &v) { // pass by constant reference
return v.empty();
}
int main() {
vector<int> w(2500);
for (int i=0; i<10'000'000; i++)
foo(w);
cout << "Done.\n";
}
Done.
Real time: 6.04 ms
No copies were made! 😎
Rule of thumb
- When sending data to a function:
- When the function is supposed to change the argument:
- Pass by non-constant reference.
- I/O streams always get passed this way.
double sqrt(double d);
int count_spaces(const string &s);
void get_physical_info(int &height, int &weight);
void remove_spaces(string &s);
void read_picture(Picture &pic, ostream &os);
void write_picture(const Picture &pic, ostream &os);
Arrays
Arrays are special. They’re always passed by reference:
void foo(int a[]) {
a[0] = 911;
}
int main() {
int numbers[] = {555-1212, 867-5309}; // dashes⁇
foo(numbers);
cout << numbers[0];
}
911
This only applies to old-style C arrays,
not std::vector or std::array.