Contact
Evil Genius SocietyHomeLearnForumLeaderboardSocialsMessages
← Back to module

Lesson 3 of 8

Lesson 5.2 ended on a frustration: swap_values couldn't swap anything, because it only ever saw copies. References fix that.

A reference is another name for the same object

#include <iostream>

int main() {
    int original = 10;
    int& alias = original;      // alias IS original, under another name

    alias = 20;
    std::cout << original << " " << alias << "\n";

    original = 30;
    std::cout << original << " " << alias << "\n";
    return 0;
}
20 20
30 30

There is only one int here. alias isn't a copy and isn't a pointer — it's a second label on the same box. Change either name and you've changed the one object.

Two rules follow:

  • A reference must be initialized when declared. There's no such thing as a reference to nothing.
  • It can never be reseated. alias = 20; assigns through the reference; it doesn't make alias refer to something else. A reference is bound for life.

Passing by reference

Put & in the parameter list and the function gets the caller's actual object:

#include <iostream>

void set_to_ninety_nine(int& value) {
    value = 99;
}

int main() {
    int number = 5;
    set_to_ninety_nine(number);
    std::cout << number << "\n";
    return 0;
}
99

Compare with lesson 5.2's version, which printed 5. The only difference is one &.

Now the swap works:

#include <iostream>

void swap_values(int& a, int& b) {
    int temp = a;
    a = b;
    b = temp;
}

int main() {
    int x = 1;
    int y = 2;

    swap_values(x, y);
    std::cout << "x=" << x << " y=" << y << "\n";
    return 0;
}
x=2 y=1

The call site looks identical

This is the thing to be careful about:

modify(number);      // by reference — number may change
inspect(number);     // by value — number cannot change

Nothing at the call site tells you which. Unlike some languages, C++ has no ref keyword at the point of call — you have to look at the function's declaration.

That's a real readability cost, and it's the argument for the discipline in lesson 5.4: use const& when you're only reading, so that a plain & becomes a reliable signal that the function will modify the argument.

Two reasons to pass by reference

1. To modify the caller's object. The examples above.

2. To avoid copying. Often the more important one:

#include <iostream>
#include <string>

void by_value(std::string text) {
    text += "!";                  // modifies the copy
}

void by_reference(std::string& text) {
    text += "!";                  // modifies the original
}

int main() {
    std::string a = "hello";
    std::string b = "hello";

    by_value(a);
    by_reference(b);

    std::cout << a << "\n";
    std::cout << b << "\n";
    return 0;
}
hello
hello!

by_value copied the whole string, appended to the copy, and threw it away.

References can't bind to temporaries

A non-const reference requires an actual object with a name:

// WILL NOT COMPILE
void modify(int& value) {
    value = 10;
}

int main() {
    modify(5);          // 5 is a temporary — nothing to modify
    return 0;
}

error: cannot bind non-const lvalue reference of type 'int&' to an rvalue of type 'int'

This makes sense: the function promises to write to its argument, and 5 isn't somewhere you can write. A const& can bind to a temporary, which is one more reason it's the better default.

Returning a reference

Functions can return references too — usually to something the caller already owns:

#include <iostream>

int& largest(int& a, int& b) {
    return (a > b) ? a : b;
}

int main() {
    int x = 3;
    int y = 8;

    largest(x, y) = 100;        // assigns to whichever was larger
    std::cout << x << " " << y << "\n";
    return 0;
}
3 100

largest(x, y) = 100; looks strange but follows directly: the call is y, so assigning to it assigns to y. This is how vector's operator[] lets you write v[0] = 5;.

The rule from lesson 5.2 still stands absolutely: never return a reference to a local. Here a and b are references to the caller's objects, which outlive the call.

Choosing

| Pass as | When | | --- | --- | | T (value) | small and cheap (int, char, double, bool), and you don't need to modify the caller's | | const T& | you only read, and copying would be wasteful — the default for strings and objects | | T& | you genuinely need to modify the caller's object |

Try it

Write void add_and_report(int& total, int amount) that adds amount to total and prints the new value. Note that one parameter is a reference and one isn't — and be able to say why.

Checkpoint

  • A reference (T&) is another name for an existing object — no copy, no pointer.
  • References must be initialized and can never be rebound.
  • A reference parameter lets the function modify the caller's object.
  • The call site looks the same either way; you must read the declaration.
  • Non-const references can't bind to temporaries like 5.
  • Never return a reference to a local variable.

Questions about this lesson

Log in to ask a question.