C++ / CLASSES AND OBJECT LIFECYCLE
The this pointer and fluent interfaces
Use the implicit this pointer to name the current object, and return *this by reference so setter calls chain into one fluent statement.
What you will learn
- Read obj.f(x) as f(&obj, x): this is the hidden first parameter of a member call
- Return ClassName& and end each setter with return *this; to keep a chain on one object
- Write this->member when a parameter shadows the member's name
- Tell T& apart from T as a return type: only T& keeps the chain off copies
Understanding The this pointer and fluent interfaces
Every non-static member function receives one argument you never wrote: the address of the object it was called on. `a.width(10)` compiles to roughly `Label::width(&a, 10)`, and inside the body that address is available as `this`, which is why the bare name `width_` means `this->width_`. In a non-const member of `Label` the type is `Label*`, and in a const member it is `const Label*` — that type difference is the whole reason a const member cannot hand out a modifiable reference to itself. You can read and copy `this`, but you cannot assign to it, and static members, having no object behind them, have no `this` at all.
`*this` dereferences that pointer and gives you the object itself as an lvalue. Whether `return *this;` copies depends entirely on the declared return type: `Label&` binds a reference to the very object the caller named, while `Label` invokes the copy constructor and hands back a duplicate. A chain such as `a.text("total").width(10)` works because the first call's result is a `Label&` that names `a`, so the second call gets `&a` as its own `this`. The chain is therefore a series of calls on one object, not a pipeline of values.
A fluent interface is that convention applied on purpose: mutators return `ClassName&`, and the call that ends the sentence — print, build, send — returns something else. Each link needs the previous call's result, so evaluation order across the chain is well defined and left to right. Returning a reference to `*this` is safe because the object outlives the call, but only as long as it outlives the full expression too: a chain started on a temporary, like `Label().text("x")`, produces a reference that dangles the instant the semicolon destroys that temporary.
<iostream>
<string>
class Label {
public:
Label& text(const std::string& t) { text_ = t; return *this; }
Label& width(int w) { width_ = w; return *this; }
Label& fill(char c) { fill_ = c; return *this; }
const Label& print() const {
std::string out = text_;
while (static_cast<int>(out.size()) < width_) out.push_back(fill_);
std::cout << '[' << out << "]\n";
return *this; // this is const Label* here, so only const Label& can leave
}
private:
std::string text_;
int width_ = 0;
char fill_ = ' ';
};
int main() {
Label a;
a.text("total").width(10).fill('.').print();
Label* p = &a.width(12); // the setter handed back a itself, not a copy
std::cout << std::boolalpha << (p == &a) << '\n';
a.print();
}
this is a hidden pointer to the object a member function was called on, and returning *this as a reference lets each call hand that same object to the next.
Worked examples
Returning by value breaks the chain
Two classes with identical bodies differ only in return type, and only the reference version accumulates all three calls.
<iostream>
struct ByValue {
int n = 0;
ByValue add(int k) { n += k; return *this; } // a copy leaves the function
};
struct ByRef {
int n = 0;
ByRef& add(int k) { n += k; return *this; } // the caller's object leaves
};
int main() {
ByValue v;
v.add(1).add(10).add(100);
std::cout << "by value: " << v.n << '\n';
ByRef r;
r.add(1).add(10).add(100);
std::cout << "by ref: " << r.n << '\n';
}
Example explained
Line 1`v.add(1)` runs with `this == &v`, so it really does set `v.n` to 1.
Line 2Because `ByValue::add` is declared to return `ByValue`, the copy constructor runs on the way out and the caller receives a duplicate.
Line 3`.add(10)` and `.add(100)` therefore modify nameless temporaries that are destroyed at the semicolon, and their work is lost.
Line 4`ByRef::add` returns `ByRef&`, so every link in the chain has the same `this` and all three additions land in `r.n`.
this-> when a parameter shadows a member
A parameter with the member's name hides it, and the hidden pointer is how you reach the member again.
<iostream>
class Counter {
public:
void set(int value) {
value = value; // assigns the parameter to itself
}
void setFixed(int value) {
this->value = value; // member on the left, parameter on the right
}
int get() const { return value; }
private:
int value = -1;
};
int main() {
Counter c;
c.set(7);
std::cout << "after set: " << c.get() << '\n';
c.setFixed(7);
std::cout << "after setFixed: " << c.get() << '\n';
}
Example explained
Line 1Inside `set`, name lookup finds the parameter `value` before the member, so `value = value;` never touches the object.
Line 2`c.get()` still reports -1, the default member initialiser, because the member was never written.
Line 3`this->value` starts lookup in the class, so it unambiguously names the member and `setFixed` stores 7.
Line 4Compilers may warn about the self-assignment, but the code is legal — only you know which `value` you meant.
Important notes
`this` is a pointer, not a reference, because it predates references in the language: inside a member you write `this->x`, never `this.x`, and you cannot assign to `this` itself.
Since a fluent setter returns a reference, `auto copy = obj.width(3);` deduces `Label` and silently copies the object; use `auto&` when you mean to keep referring to the same one.
Common mistakes
Declaring a setter as `Label` instead of `Label&`: the chain still compiles, but only the first link edits the object and every later change is discarded with the temporary.
Writing `return this;` in a function declared to return `Label&`: `this` is a `Label*`, so it fails to compile — and if you do return `Label*`, callers are forced to chain with `->` instead of `.`.
Naming a parameter exactly like the member and then writing `value = value;`: the parameter shadows the member, the assignment is a no-op, and the object silently keeps its old value.
Try it yourself
Change, predict, then run
Extend the Label class with `margin(int)` and a `reset()` that clears the text and zeroes the width, both returning `*this`, then build and print a label in a single chained statement. Also print whether `&l` equals the address returned by `l.reset()`.
Open the C++ workspaceCheck your understanding
A class declares `Vec add(int k) { n += k; return *this; }` and a caller writes `v.add(1).add(2).add(3);` on a fresh `Vec v` whose `n` is 0. What is `v.n` afterwards?
- 6, because every call in the chain adds to v
- 1, because only the first call has this == &v; the other two add to temporaries
- 0, because a member returning by value can never modify the caller's object
- 3, because only the last call in the chain is applied to v
Show answer
The first call receives `&v` as its `this` and really sets `n` to 1; the copy happens on the way out, so `.add(2)` and `.add(3)` mutate nameless temporaries that die at the semicolon. The first option is tempting because the body `return *this;` is identical to the reference-returning version, but it is the declared return type — not the return expression — that decides whether a copy leaves the function.