Lesson 5 of 12
Every non-static member function receives a hidden pointer to the object it was called on, named
this.
#include <iostream>
class Counter {
int count = 0;
public:
void increment() {
++count; // shorthand
// ++this->count; // what it actually means
}
int value() const { return this->count; }
};
int main() {
Counter c;
c.increment();
c.increment();
std::cout << c.value() << "\n";
return 0;
}
2
Writing this-> everywhere is legal and noisy. Use the bare member name unless you need this
for one of the reasons below.
this is a pointer, not a reference, for historical reasons — references did not exist when it
was designed. In a const member function its type is const T*, which is exactly why such a
function cannot modify members.
When you actually need it
1. A parameter shadows a member.
#include <iostream>
class Point {
int x;
int y;
public:
// Parameters have the same names as the members.
Point(int x, int y) : x(x), y(y) {}
void move_to(int x, int y) {
this->x = x; // without `this->`, this would assign the parameter to itself
this->y = y;
}
void print() const { std::cout << x << "," << y << "\n"; }
};
int main() {
Point p(1, 2);
p.print();
p.move_to(10, 20);
p.print();
return 0;
}
1,2
10,20
Note the constructor's : x(x), y(y) works without this-> — in a member initializer list, the
name outside the parentheses is always the member and the name inside is the parameter. Inside a
function body there is no such rule, so x = x; assigns the parameter to itself and the member
is never touched.
GCC does not warn about this, even with -Wall -Wextra. Clang has -Wself-assign, which GCC
does not accept at all. So this is a silent bug on the most common toolchain: the code compiles,
runs, and quietly does nothing.
The reliable fix is to remove the shadowing rather than rely on a diagnostic. Many codebases name
members x_ or m_x for exactly this reason.
2. Returning the object for chaining.
#include <iostream>
#include <string>
class Builder {
std::string result;
public:
Builder& add(const std::string& part) {
result += part;
return *this; // return the object itself, by reference
}
Builder& add_space() {
result += ' ';
return *this;
}
const std::string& str() const { return result; }
};
int main() {
Builder b;
b.add("Evil").add_space().add("Genius").add_space().add("Society");
std::cout << b.str() << "\n";
return 0;
}
Evil Genius Society
return *this; dereferences the pointer to give the object, returned by reference so no copy is
made and the next call operates on the same object. This is how std::cout << a << b chains, and
how builder-style APIs work.
Returning Builder by value instead of Builder& would copy the object at every step and
each call would modify a different temporary — a bug that still compiles.
3. Passing yourself to something else.
#include <iostream>
class Widget;
void describe(const Widget& w);
class Widget {
int id;
public:
Widget(int i) : id(i) {}
int get_id() const { return id; }
void report() const {
describe(*this); // hand the whole object over
}
};
void describe(const Widget& w) {
std::cout << "widget #" << w.get_id() << "\n";
}
int main() {
Widget w(42);
w.report();
return 0;
}
widget #42
4. Checking for self-assignment. Lesson 9.7 needs this:
Buffer& operator=(const Buffer& other) {
if (this == &other) {
return *this; // assigning to itself: do nothing
}
// ... otherwise copy
return *this;
}
Comparing addresses is how you detect b = b;, which without this check would free the buffer and
then copy from it.
this in a const member
#include <iostream>
class Example {
int value = 5;
public:
void modify() {
this->value = 10; // this is Example*
}
int read() const {
// this->value = 1; // error: this is const Example*
return this->value;
}
};
int main() {
Example e;
std::cout << e.read() << "\n";
e.modify();
std::cout << e.read() << "\n";
return 0;
}
5
10
The const on the member function is applied to this. That is the whole mechanism — there is
nothing else to it.
this is never null
In well-defined code this always points at a real object, so checking if (this == nullptr) is
pointless: calling a member function through a null pointer is already undefined behaviour before
your check runs, and compilers optimise the check away on exactly that reasoning (lesson 8.7).
Try it
Write a class Vec2 with x and y, and add, scale and normalise_sign members that each
return Vec2& so calls can chain. Use this-> where a parameter shadows a member.
Checkpoint
- Every non-static member function gets a hidden
thispointing at the object. - Member access is implicitly
this->member; write it out only when needed. - In a
constmember,thisisconst T*— that is howconstis enforced. return *this;by reference enables call chaining.this == &otherdetects self-assignment (lesson 9.7).- Use
this->when a parameter shadows a member, or rename members to avoid the clash.
Questions about this lesson
Log in to ask a question.