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