#119 – The hidden this pointer

The problem

Consider this class:

#include <iostream>
#include <string>
#include <string_view>

class AudioChannel {
private:
    std::string m_name {};
    int m_volume {};

public:
    AudioChannel(std::string_view name, int volume)
        : m_name { name }
        , m_volume { volume }
    {
    }

    void setVolume(int volume){
        m_volume = volume;
    }

    void print() const{
        std::cout << m_name << ": "
                  << m_volume << "%\n";
    }
};

int main(){
    AudioChannel music { "Music", 35 };
    AudioChannel effects { "Effects", 80 };

    music.setVolume(50);
    effects.setVolume(20);

    music.print();
    effects.print();
}

Terminal output:
Music: 50%
Effects: 20%

Both objects call the same member function:

setVolume()

But this call modifies music:

music.setVolume(50);

and this call modifies effects:

effects.setVolume(20);

How does C++ know which object m_volume belongs to?

The answer is the hidden this pointer.

What this means

Inside a non-static member function, this is a pointer to the object that called the function.

This function:

void setVolume(int volume){
    m_volume = volume;
}

can be written explicitly as:

void setVolume(int volume){
    this->m_volume = volume;
}

The expression: this->m_volume means:

Access m_volume in the object currently calling this member function.

So when this runs: music.setVolume(50);

this points to music.

this in const member functions

In a non-const member function, this behaves like a pointer to a modifiable object.

In a const member function, this behaves like a pointer to const.

That is why this works:

void print() const{
    std::cout << this->m_name << ": "
              << this->m_volume << "%\n";
}

but this would not:

void print() const{
    this->m_volume = 100; // error: cannot modify object inside const member function
}

The const after the parameter list applies to the object being pointed to by this.

So this member function:

void print() const

promises not to modify the object.

Method chaining with this

The this pointer can also be used to return the current object.

This enables method chaining.

#include <iostream>
#include <string>
#include <string_view>

class AudioChannel {
private:
    std::string m_name {};
    int m_volume {};
    bool m_muted {};

public:
    AudioChannel(std::string_view name)
        : m_name { name }
    {
    }

    AudioChannel& setVolume(int volume){
        m_volume = volume;
        return *this;
    }

    AudioChannel& mute(){
        m_muted = true;
        return *this;
    }

    AudioChannel& unmute(){
        m_muted = false;
        return *this;
    }

    void print() const{
        std::cout << m_name << ": "
                  << m_volume << "%, "
                  << (m_muted ? "muted" : "unmuted") << '\n';
    }
};

int main(){
    AudioChannel music { "Music" };

    music.setVolume(65).mute().unmute();

    music.print();
}

Terminal output:
Music: 65%, unmuted

The key return type is: AudioChannel&

This call: music.setVolume(65) returns music by reference.

That allows the next call and the next call. The chain works because every function returns *this.

The alternative without method chaining is:

AudioChannel music { "Music" };
music.setVolume(65);
music.mute();
music.unmute();
music.print();

Each statement occupies a separate line.

Resetting an object with this

#include <iostream>
#include <string>
#include <string_view>

class AudioChannel {
private:
    std::string m_name { "Unnamed" };
    int m_volume {};
    bool m_muted {};

public:
    AudioChannel() = default;

    AudioChannel(std::string_view name)
        : m_name { name }
    {
    }

    AudioChannel& setVolume(int volume){
        m_volume = volume;
        return *this;
    }

    AudioChannel& mute(){
        m_muted = true;
        return *this;
    }

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

    void print() const{
        std::cout << m_name << ": "
                  << m_volume << "%, "
                  << (m_muted ? "muted" : "unmuted") << '\n';
    }
};

int main(){
    AudioChannel music { "Music" };

    music.setVolume(75).mute();
    music.print();

    music.reset();
    music.print();
}

Terminal output:
Music: 75%, muted
Unnamed: 0%, unmuted

Takeaway

Every non-static member function has a hidden this pointer.

It points to the object that called the function, which is why the same member function can be shared by different class instances.