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-with-an-int-argument
.
- Not really—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++ -Wall -c ~cs253/Example/overload.cc
$ nm --numeric-sort overload.o 2>&-
0000000000000000 T _Z3sumii
0000000000000014 T _Z3sumi
000000000000002e T _Z3sumv
0000000000000039 T _Z3sumdis
$ nm --numeric-sort --demangle overload.o 2>&-
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++ -Wall -c ~cs253/Example/mangle.cc
$ nm mangle.o 2>&-
0000000000000000 T _Z1bonehead
I do not expect you to memorize the various encodings
for types. It varies between compilers, anyway.
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
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'
As before, avoid pointer arguments in favor of reference arguments.
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
- Let’s talk about how arguments get passed to a function,
on today’s computers.
- 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.96 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: 7.98 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 automatically 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.