C++ Part I · Module 11 · Classes and Encapsulation
this and How a Member Function Finds Its Object
Chapter 11.3 established that a member function receives the object it was called on as a hidden argument, and left the argument unnamed. That omission has been costing something: three chapters have now written const on member functions without being able to say what it means, because const in that position is not a statement about any parameter in the parameter list — it is a statement about the hidden argument, so the keyword cannot be explained until the argument has a name. This chapter gives it one. The concept is modest and the measurements around it are the interesting part. The hidden argument is a pointer, it holds exactly the address of the object the function was called on, and it is emphatically not a member of that object, which can be demonstrated by the fact that a class containing one four-byte integer measures four bytes and has nowhere to put a pointer. The same experiment turns up two results worth knowing for their own sake: an empty class does not measure zero bytes, for a reason that follows from objects needing distinct addresses, and a pointer to a member function of a four-byte class measured sixteen bytes. The chapter closes on the compiler error that bridges to the next one, which is unusually helpful because it names the hidden argument explicitly rather than talking about the function.
Intermediate16 min readC++Part IFundamentalsClasses
Part I · Module 11 · Chapter 11.6 · this and How a Member Function Finds Its Object
1. The Question
Counter a, b;
a.bump();
b.bump();bump() is one function. Chapter 11.1 §4 measured that ten member functions add zero bytes to an object, so there is not a copy of it per object. Yet a.bump() changes a's member and b.bump() changes b's — Chapter 11.2 §7 measured three objects holding 3, 1 and 0.
So how does the one function know which object to act on?
2. The Hidden Argument
Chapter 11.3 §5 gave the answer in words: the object arrives as a hidden argument. It has a name.
Inside a non-static member function,
thisis a pointer to the object the function was called on.
Two ways of writing the same thing:
int Counter::value() const { return n_; } // what you write
int Counter::value() const { return this->n_; } // what it meansAn unqualified member name inside a member function is implicitly this->. That is the whole mechanism.
Its type depends on the function:
| Member function | Type of this |
|---|---|
void bump() | Counter* |
int value() const | const Counter* |
The second row is the entire content of Chapter 11.7, previewed in §7.
3. It Is the Object's Address — Measured
class Counter {
public:
void report() const {
std::printf(" object at %p this = %p equal? %s n=%d\n",
static_cast<const void*>(this), static_cast<const void*>(this),
"yes", n_);
}
void bump() { ++n_; }
int n() const { return n_; }
private:
int n_ = 0;
};a at 0x16bc32718 b at 0x16bc32714
a.n()=2 b.n()=1 (same function, different objects)
object at 0x16bc32718 this = 0x16bc32718 equal? yes n=2
object at 0x16bc32714 this = 0x16bc32714 equal? yes n=1
after a.setN(42): a.n()=42 b.n()=1
sizeof(Counter) = 4 (no pointer stored in the object)And the exact comparison, which is better evidence than printing two matching numbers:
class T { public: const void* addr() const { return this; } };
T t;
// &t == t.addr()?&t == t.addr()? truethis is the address of the object, exactly. Not a handle, not an index, not a copy — the same pointer you would get from &a.
4. It Is Not Stored in the Object
The last line of §3's output is the one that matters:
sizeof(Counter) = 4Counter has one int member and measures 4 bytes. A pointer on this platform is 8 bytes — Chapter 8.1 measured it. There is no room for this in the object, so it is not there.
thisis an argument passed at the call, not a member of the object.
That is why member functions cost nothing: Chapter 11.1 §4's ten functions added 0 bytes because there is nothing per-object to add. The call a.value() passes &a; the call b.value() passes &b; the function is one piece of code that works on whatever address it is handed.
Two further measurements from the same experiment, both worth knowing independently.
An empty class is not zero bytes.
arr[0..2].addr(): 0x16d782728 0x16d782729 0x16d78272a (stride 1)
sizeof(T) = 1 (empty class)T has no data members at all and measures 1 byte, with array elements one byte apart. The reason follows from this: distinct objects must have distinct addresses, so an array of them must give each one a different byte. A size of zero would make &arr[0] and &arr[1] equal.
And a pointer to a member function is larger than the object. Chapter 11.3 §6's measurement:
sizeof(C) = 4 sizeof(&C::a) = 16Four times the size of the object it belongs to — which is the most direct proof available that the code is not inside the object. (Why 16 rather than 8 is a consequence of how such pointers must work when virtual functions are involved, which is Module 17's subject.)
5. When You Need to Write It
Most of the time you do not. n_ already means this->n_, and writing this->n_ everywhere adds noise without adding information — which is what the chapter's purpose means by where it genuinely clarifies. There are four cases where it earns its place.
A member hidden by a parameter of the same name. Measured working:
void setN(int n_) { this->n_ = n_; } // a.n() became 42Inside that function, the unqualified n_ is the parameter — Chapter 7.1 §6's shadowing rule, applied to members. this->n_ is the only way to reach the member. This is also the reason for the trailing-underscore convention introduced in Chapter 11.2 §2: name the member n_ and the parameter n, and the collision never arises.
Returning the object itself, for an operation meant to be chained or for assignment:
Counter& bumpTwice() { ++n_; ++n_; return *this; }*this is the object; this is its address. Chapter 15.6 is where this becomes necessary rather than stylistic, because assignment operators are required to return *this.
Passing the object to something that takes a pointer or reference to it — registering it as an observer, handing it to a function that needs the whole object rather than its parts.
Comparing identity, as in checking for self-assignment:
if (this == &other) return *this; // the same objectThat is Chapter 13.3's subject and it is the classic use.
Outside those four, prefer the unqualified name.
6. Two Things It Is Not
It is not available in a static member function. A static member function belongs to the type rather than to any object, so there is nothing for this to point at. Measured:
class C { public: int n_ = 1; static int f() { return n_; } };error: invalid use of member 'n_' in static member functionThe message is about the member, not about this, which is slightly indirect — the real reason is that there is no object. Chapter 11.8 is static members properly.
And it is not reassignable. this is a pointer value, not a variable you can point elsewhere; there is no meaningful operation that makes a member function act on a different object mid-call.
7. Why const on a Member Function Is About this
Now the debt from Chapters 11.2, 11.3 and 11.5 can be paid, and the compiler explains it better than prose would.
const Reg r(4);
r.widen(); // widen() is not marked consterror: 'this' argument to member function 'widen' has type 'const Reg',
but function is not marked constThe error is about this. Not about r, not about a parameter — about the hidden argument. r is a const Reg, so calling a member function on it makes this a const Reg*, and widen() declares that its this is a plain Reg*. The types do not match, and the call is rejected.
The same thing through a reference, which is how it actually happens in real code:
Reg r(4);
const Reg& cr = r;
cr.widen(); // identical errorThat second form is why Chapter 11.7 is rated Critical. Chapter 6.5 established const T& as the default way to pass anything non-trivial, so every member function a caller needs to call on a const reference must be marked const — and a type whose observers are not marked cannot usefully be passed by const reference at all.
So the grammar makes sense now:
int value() const { return n_; }
// ^^^^^ a statement about `this`, which is why it sits after
// the parameter list rather than inside itThe keyword's odd position — after the parentheses, where nothing else goes — is because it qualifies an argument that does not appear in the parentheses.
8. What to Actually Do
Write unqualified member names. n_, not this->n_, unless one of §5's four cases applies.
Use the trailing-underscore convention for members so a parameter never shadows one.
Write this-> when a parameter does shadow a member, or rename the parameter.
Return *this from operations meant to chain, and from assignment operators (Chapter 15.6).
Use this == &other for self-assignment checks (Chapter 13.3).
Do not expect this in a static member function — there is no object.
Mark every non-mutating member function const, because that is a statement about this and it determines whether a caller holding a const reference can use your type at all.
And do not treat the member-function-pointer size as evidence of anything beyond the inequality.
9. Mistakes Worth Making Once
Writing this-> everywhere. Noise; the purpose says where it genuinely clarifies.
Naming a parameter the same as a member and assigning it to itself. n_ = n_; assigns the parameter to itself and silently does nothing — the trailing underscore prevents it.
Expecting this in a static member function. error: invalid use of member 'n_' in static member function.
Returning this where *this was meant. One is a pointer, the other is the object.
Assuming this is stored in the object. sizeof(Counter) is 4 and a pointer is 8.
Expecting an empty class to be 0 bytes. Measured 1, because objects need distinct addresses.
Reading the const error as being about the object. It is about the this argument, which is why the message says so.
Explaining the 16-byte member pointer with a guess. It needs Module 17.
10. Interview Relevance
"What is this?" A pointer to the object a non-static member function was called on — passed as a hidden argument, not stored in the object. The second clause is what distinguishes a real answer.
"Is this a member of the class?" No, and sizeof proves it: a class with one int is 4 bytes and a pointer is 8.
"Why is const written after the parameter list?" Because it qualifies this, which is not in the parameter list. Very few candidates can explain the position, and the compiler's error message — "'this' argument … has type 'const Reg'" — is the evidence.
"When do you need to write this explicitly?" Shadowed member names, returning *this, passing the object onward, and self-assignment checks. A short honest list beats a vague "when you want to be clear".
"Why is sizeof of an empty class not zero?" Because distinct objects must have distinct addresses, so an array of them needs at least one byte each. Measured: size 1, stride 1.
"Can a static member function use this?" No — there is no object. The error names the member rather than this, which is a small gotcha.
11. What You Should Be Able to Say Now
thisis a pointer to the object a non-static member function was called on, passed as a hidden argument.- An unqualified member name means
this->implicitly. - Measured:
&t == t.addr()was true —thisis exactly the object's address. - Measured:
sizeof(Counter)was 4 with oneintmember, and a pointer is 8 — sothisis not stored in the object. - That is why member functions cost 0 bytes (Chapter 11.1).
- Measured: an empty class is 1 byte with stride 1, because distinct objects need distinct addresses.
- Measured:
sizeof(&C::a)was 16 against a 4-byte object — the code is not in the object. The reason for 16 specifically is Module 17's. - Its type depends on the function:
Counter*for a non-constmember,const Counter*for aconstone. - Write it explicitly in four cases: shadowed names, returning
*this, passing the object on, identity comparison. - The trailing-underscore convention makes the shadowing case unnecessary.
- No
thisin a static member function —error: invalid use of member 'n_' in static member function. conston a member function is a statement aboutthis, which is why it sits after the parameter list.- Measured: the error says
'this' argument to member function 'widen' has type 'const Reg'— and the same error occurs through aconst Reg&. - So a type whose observers are not
constcannot be passed byconstreference usefully — Chapter 11.7.
12. Exercises
Exercise 1 — rewrite it. Rewrite int value() const { return n_; } with every implicit this-> made explicit, and say whether you would ship it that way.
Exercise 2 — the type of this. Give the type of this inside each: void bump(), int value() const, and a static int count().
Exercise 3 — the shadowing bug. void setWidth(int width_) { width_ = width_; } — say exactly what it does, and give two fixes.
Exercise 4 — predict. Which compile: (a) this->n_ in a const member (b) ++n_ in a const member (c) this in a static member (d) return *this; from a member returning Counter& (e) return this; from the same.
Exercise 5 — the empty class. Explain why sizeof of a class with no members is 1 and not 0, in terms of this.
Exercise 6 — read the error. Explain, to a colleague, what 'this' argument to member function 'widen' has type 'const Reg' means and what to change.
Exercise 7 — the restraint. Given sizeof(C) = 4 and sizeof(&C::a) = 16, state what you may conclude and what you may not.
13. Summary
thisis a pointer to the object the member function was called on, supplied as a hidden argument; an unqualified member name meansthis->.- Measured: it equals the object's address exactly (
&t == t.addr()→ true). - Measured:
sizeof(Counter)4 with a 4-byte member while a pointer is 8 —thisis not in the object, which is why member functions are free. - Measured: an empty class is 1 byte, because distinct objects need distinct addresses.
- Measured:
sizeof(&C::a)16 against a 4-byte object — proof the code is elsewhere; the reason for 16 belongs to Module 17. - Its type is
T*orconst T*depending on the function. - Write it explicitly only for shadowed names,
*this, passing the object on, and identity checks. - No
thisin a static member function. conston a member function qualifiesthis— hence its position, and hence the error naming'this' argument.- The same error arises through a
const T&, which is whyconstmembers matter for every caller.
The hidden argument is named and the keyword that has been deferred three times is now explicable, but only explicable — the previous section showed what the error means and not what to do about it, and "mark the non-mutating ones" is a rule without a reason until you have seen what breaks without it. What breaks is larger than it sounds. Chapter 6.5 established passing by const reference as the default for anything that is not a cheap scalar, and Chapter 9.6 measured what the alternative costs in allocations; a type that has not marked its observers cannot be passed that way at all, so the omission does not produce a small inconvenience in one call but makes the type unusable in the position most of its callers will want. The next chapter is that rule with its reasons, including the measured overload resolution that lets one name serve both a mutable and an immutable object, and the single escape hatch for a member that must change even on a const object.
Standards & specifications
- Governing standard
- ISO/IEC 14882 (the C++ language standard)(opens ISO/IEC (JTC 1/SC 22/WG 21) in a new tab)
Defines the C++ language and its standard library — the type system and templates, the object model and object lifetime, initialisation and overload resolution, the memory model and the containers, algorithms and utilities the library provides. Like C's standard it is a FAMILY of editions (C++98/03, C++11, C++14, C++17, C++20, C++23) and the editions are NOT interchangeable: a feature this curriculum relies on may be unavailable, differently specified or merely proposed in another edition, so each lesson names the edition its claim depends on. It does not define any foreign-function interface to a hardware description language, nor what a compiler actually emits, nor platform behaviour on real silicon — those are the implementation's and the device's contracts. Working drafts published by WG21 are freely available and are what most engineers read day to day; a draft is not the published standard.
This page also covers RTL structure, verification approach and debugging technique. Those are engineering practice built on the standard, not requirements the standard itself imposes.
Where this fits
Part of the C++ Programming curriculum.
