Skip to content
VLSI Mentor

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

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    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, this is a pointer to the object the function was called on.

Two ways of writing the same thing:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    int Counter::value() const { return n_; }          // what you write
    int Counter::value() const { return this->n_; }    // what it means

An unqualified member name inside a member function is implicitly this->. That is the whole mechanism.

Its type depends on the function:

Member functionType of this
void bump()Counter*
int value() constconst Counter*

The second row is the entire content of Chapter 11.7, previewed in §7.

3. It Is the Object's Address — Measured

Azvya Education Pvt. Ltd.VLSI Mentor
thisptr.cpp — the relevant part
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;
};
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    class T { public: const void* addr() const { return this; } };
    T t;
    // &t == t.addr()?
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
&t == t.addr()? true

this 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:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
sizeof(Counter) = 4

Counter 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.

this is 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.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
sizeof(C) = 4   sizeof(&C::a) = 16

Four 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:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    void setN(int n_) { this->n_ = n_; }      // a.n() became 42

Inside 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:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    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:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    if (this == &other) return *this;      // the same object

That 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:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    class C { public: int n_ = 1; static int f() { return n_; } };
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
error: invalid use of member 'n_' in static member function

The 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.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    const Reg r(4);
    r.widen();            // widen() is not marked const
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
error: 'this' argument to member function 'widen' has type 'const Reg',
       but function is not marked const

The 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:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    Reg r(4);
    const Reg& cr = r;
    cr.widen();          // identical error

That 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:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    int value() const { return n_; }
    //          ^^^^^ a statement about `this`, which is why it sits after
    //                the parameter list rather than inside it

The 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

  1. this is a pointer to the object a non-static member function was called on, passed as a hidden argument.
  2. An unqualified member name means this-> implicitly.
  3. Measured: &t == t.addr() was true — this is exactly the object's address.
  4. Measured: sizeof(Counter) was 4 with one int member, and a pointer is 8 — so this is not stored in the object.
  5. That is why member functions cost 0 bytes (Chapter 11.1).
  6. Measured: an empty class is 1 byte with stride 1, because distinct objects need distinct addresses.
  7. 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.
  8. Its type depends on the function: Counter* for a non-const member, const Counter* for a const one.
  9. Write it explicitly in four cases: shadowed names, returning *this, passing the object on, identity comparison.
  10. The trailing-underscore convention makes the shadowing case unnecessary.
  11. No this in a static member function — error: invalid use of member 'n_' in static member function.
  12. const on a member function is a statement about this, which is why it sits after the parameter list.
  13. Measured: the error says 'this' argument to member function 'widen' has type 'const Reg' — and the same error occurs through a const Reg&.
  14. So a type whose observers are not const cannot be passed by const reference 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

  • this is a pointer to the object the member function was called on, supplied as a hidden argument; an unqualified member name means this->.
  • 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 — this is 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* or const T* depending on the function.
  • Write it explicitly only for shadowed names, *this, passing the object on, and identity checks.
  • No this in a static member function.
  • const on a member function qualifies this — hence its position, and hence the error naming 'this' argument.
  • The same error arises through a const T&, which is why const members 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.