What Is the this Pointer and Member Function Chaining?

A class is written once. Its member functions are compiled once. Yet a program can create a hundred objects of that class, and every call has to land on the right one. this is how that works: a const pointer, handed to every non-static member function without you ever declaring it, carrying the address of the object the call was made through. Once that pointer is visible to you, a second technique becomes available, because a member function that returns *this can be called again on the result, letting a run of calls join into one expression.

Two objects, one function body

Here are two ticket gates at a station, each keeping its own count of people who have walked through:

#include <iostream>

class Turnstile
{
private:
    int m_passages{};

public:
    void admit(int riders) { m_passages += riders; }
    int passages() const { return m_passages; }
};

int main()
{
    Turnstile northGate{};
    Turnstile southGate{};

    northGate.admit(3);
    southGate.admit(11);
    northGate.admit(2);

    std::cout << "North gate: " << northGate.passages() << '\n';
    std::cout << "South gate: " << southGate.passages() << '\n';

    return 0;
}

Output:

North gate: 5
South gate: 11

There is one admit() in the program. It ran three times, twice on behalf of northGate and once on behalf of southGate, and each run reached the correct m_passages. Something has to be telling it which counter is meant, and the body of admit() clearly does not say.

The pointer you never declared

Every non-static member function takes one more argument than its declaration shows. Ahead of everything you wrote sits this: a const pointer holding the address of the object the call was made through. The const applies to the pointer rather than to what it points at, so this can never be aimed elsewhere partway through a call.

You do not have to take that on faith. Compare this against the address of another object of the same type and the answer tells you whether the two are the same object:

#include <iostream>

class Turnstile
{
private:
    int m_passages{};

public:
    void admit(int riders) { m_passages += riders; }
    int passages() const { return m_passages; }

    bool isSameGate(const Turnstile& gate) const { return this == &gate; }
};

int main()
{
    Turnstile northGate{};
    Turnstile southGate{};

    std::cout << std::boolalpha;
    std::cout << "north asked about north: " << northGate.isSameGate(northGate) << '\n';
    std::cout << "north asked about south: " << northGate.isSameGate(southGate) << '\n';
    std::cout << "south asked about south: " << southGate.isSameGate(southGate) << '\n';

    std::cout << "sizeof(Turnstile): " << sizeof(Turnstile) << '\n';
    std::cout << "sizeof(int): " << sizeof(int) << '\n';

    return 0;
}

Output:

north asked about north: true
north asked about south: false
south asked about south: true
sizeof(Turnstile): 4
sizeof(int): 4

The middle line is the interesting one: called through northGate, this compares equal to &northGate and unequal to &southGate. Whichever object precedes the dot is the one this refers to for the duration of that call.

The two sizeof lines make a separate point. A Turnstile weighs exactly as much as the single int it stores. this is an argument of the call, not a field of the object, so a class can have fifty member functions without any of its objects growing by a single byte.

Key Concept
Every non-static member function receives a const pointer named `this` that holds the address of the object it was called on.

What the compiler writes for you

Inside a member function, writing a member's name and writing it with this-> in front amount to the same thing:

    int passages() const { return m_passages; }
    int passages() const { return this->m_passages; }

The second spelling is what the first one means. While compiling, any name that refers to a member of the current object gets this-> attached to it, which is why nobody has to type it out and why classes stay readable.

Reminder
The arrow is member selection through a pointer, so `this->m_passages` and `(*this).m_passages` compile to precisely the same thing. The arrow exists because the parenthesised spelling is unpleasant to read.

The same rewriting happens at the call site. This statement:

    northGate.admit(3);

is not really a call with one argument. What precedes the dot becomes the leftmost argument of an ordinary function call:

    Turnstile::admit(&northGate, 3);

and the definition of admit() grows a leftmost parameter to receive it, roughly like this:

    static void admit(Turnstile* const this, int riders) { this->m_passages += riders; }

Neither sketch is code you could type. Naming a parameter this is illegal, and how a compiler really performs the rewrite is its own business. The shape, though, is accurate: the address goes in as an argument, and member names are reached through it. Trace it once and the puzzle from the opening section dissolves. northGate.admit(2) passes &northGate, this receives that address, this->m_passages resolves to northGate.m_passages, so 2 lands in the north gate's counter.

For Advanced Readers
The `static` in that sketch means the rewritten function belongs to no particular object; it is a plain function that happens to live in the class's scope. Static member functions are covered in their own lesson later in this chapter.

One pointer per call, pointing at the caller

It is tempting to imagine a collection of this pointers kept somewhere. There is no collection. this is a parameter, so it is filled in afresh at each call and holds one address for the length of that call:

    Turnstile northGate{};  // this == &northGate while northGate is built
    Turnstile southGate{};  // this == &southGate while southGate is built

    northGate.admit(3);     // this == &northGate during admit()
    southGate.admit(11);    // this == &southGate during admit()

Since this is always the address of a real object, code inside a member function never has to check it for null before dereferencing it.

When writing this-> yourself pays off

Two situations make the pointer worth naming out loud. The first is a name collision. A parameter with the same name as a data member hides that member, so the bare name refers to the parameter; this-> reaches past the parameter to the member:

#include <iostream>

struct Waypoint
{
    int altitude{};

    void setAltitude(int altitude) { this->altitude = altitude; }
};

int main()
{
    Waypoint ridge{};
    ridge.setAltitude(1420);

    std::cout << "Altitude: " << ridge.altitude << '\n';

    return 0;
}

Output:

Altitude: 1420

Waypoint is a struct, so its data has no m_ in front of it and the collision is possible in the first place. That is exactly the collision the m_ convention exists to prevent, and it is the better fix of the two. Some programmers go the other way and put this-> on every member reference in the codebase for the sake of visibility; the six extra characters buy very little, and we would steer you away from it.

Handing the object back with return *this

The second situation is the one that changes how code reads, and you have been on the receiving end of it since your first program. This line:

    std::cout << "North gate: " << northGate.passages() << '\n';

groups as ((std::cout << "North gate: ") << northGate.passages()) << '\n'. The leftmost << has to produce something for the next << to be applied to, and what it produces is the stream itself. Had it produced nothing, the second << would have no left operand to work with and the line would not compile. Because the stream comes back out, std::cout needs writing only once no matter how many pieces you send.

Your own classes can behave that way. Consider a scorekeeper for one round of a game:

#include <iostream>

class RoundScore
{
private:
    int m_points{};

public:
    void award(int amount) { m_points += amount; }
    void bonus(int factor) { m_points *= factor; }
    void penalty(int amount) { m_points -= amount; }

    int points() const { return m_points; }
};

int main()
{
    RoundScore tally{};
    tally.award(6);
    tally.bonus(3);
    tally.penalty(4);

    std::cout << tally.points() << '\n';

    return 0;
}

Output:

14

Three adjustments to one object, three statements, because each of those functions returns nothing and so ends its own expression. Give them a return type of RoundScore& and a return *this; instead:

class RoundScore
{
private:
    int m_points{};

public:
    RoundScore& award(int amount) { m_points += amount; return *this; }
    RoundScore& bonus(int factor) { m_points *= factor; return *this; }
    RoundScore& penalty(int amount) { m_points -= amount; return *this; }

    int points() const { return m_points; }
};

*this dereferences the hidden pointer, so what comes back is the object the call ran on, by reference and without a copy. The three statements now fold into one:

    tally.award(6).bonus(3).penalty(4);

Calling several member functions on one object in a single expression like this is function chaining, also called method chaining. Read it strictly left to right. tally.award(6) raises the score to 6 and yields tally, so .bonus(3) is applied to tally and triples the score to 18, again yielding tally, so .penalty(4) takes it down to 14 and yields a reference that nothing else uses. The arithmetic is (0 + 6) * 3 - 4.

Best Practice
Where a class offers several small mutators that callers naturally apply one after another, give them a reference return type and end them with `return *this;`. It costs nothing at runtime and turns a stack of statements into one readable line. Functions that answer a question rather than adjust the object should keep returning the answer.

Wiping an object clean with *this = {}

Say the round ends and the same RoundScore should serve the next one. Constructors cannot help: their job is bringing new objects into existence, and, as covered in the lesson on delegating constructors, invoking one on an object that already exists leads somewhere unpleasant. Assignment is the right tool. On the right of an assignment, {} builds a value-initialized temporary of the same type, and assigning that over the current object resets every member to its default:

    void reset() { *this = {}; }

In full:

#include <iostream>

class RoundScore
{
private:
    int m_points{};

public:
    RoundScore& award(int amount) { m_points += amount; return *this; }
    RoundScore& bonus(int factor) { m_points *= factor; return *this; }
    RoundScore& penalty(int amount) { m_points -= amount; return *this; }

    int points() const { return m_points; }

    void reset() { *this = {}; }
};

int main()
{
    RoundScore tally{};
    tally.award(6).bonus(3).penalty(4);

    std::cout << "Round total: " << tally.points() << '\n';

    tally.reset();

    std::cout << "After reset: " << tally.points() << '\n';

    return 0;
}

Output:

Round total: 14
After reset: 0

Calling a member function on a const object

Whether the object is const decides what this is allowed to touch:

Kind of member function Type of this What it permits
non-const const pointer to a non-const object members may be read and written
const const pointer to a const object members may be read only

Either way the pointer itself is const and cannot be re-aimed. What varies is the const-ness of what it addresses, and that is why a const object can only call const member functions.

The second statement below does not compile.

    const Turnstile sealedGate{};

    std::cout << "Sealed gate: " << sealedGate.passages() << '\n';
    sealedGate.admit(1);

The compiler's wording takes some unpacking. GCC rejects the call with passing 'const Turnstile' as 'this' argument discards qualifiers, which says: the call site holds the address of a const Turnstile, admit() is asking for the address of a modifiable one, and throwing away a const qualifier is not a conversion C++ performs behind your back. Mark admit() const and the complaint moves to the assignment inside it, which is the honest error, since a sealed gate genuinely cannot count anyone through.

A pointer only for historical reasons

this is never null, short of undefined behavior having already happened elsewhere, and it is never re-aimed. Both of those describe a reference far better than they describe a pointer, so why is it a pointer? Purely chronology: references were not yet part of C++ when this was introduced. Languages that borrowed the idea afterwards, Java and C# among them, made it a reference, and C++ would too if the decision were being taken today.

Summary

  • Every non-static member function is handed a hidden const pointer named this, holding the address of the object the call was made through.
  • The compiler writes this-> in front of member names for you, so m_passages and this->m_passages mean the same thing, and this->x is shorthand for (*this).x.
  • A call such as northGate.admit(3) is rewritten as Turnstile::admit(&northGate, 3), with the object passed by address into the hidden leftmost parameter.
  • this is a parameter rather than a data member, so it is refilled at every call and adds nothing to the size of an object.
  • Naming this explicitly is worth it in two places: reaching a member that a parameter of the same name is hiding, and returning the object itself.
  • A member function returning *this by reference lets calls be chained, as in tally.award(6).bonus(3).penalty(4);, with each call adjusting the object and handing it on to the next.
  • *this = {}; inside a reset() function assigns a value-initialized temporary over the object, returning it to its default state without calling a constructor directly.
  • In a const member function this points at a const object, which is what stops a const object from calling functions that would modify it.
  • this is a pointer for historical reasons alone; a reference is what it would be if the language were designed now.