this, static members, and factories
Classes and object lifetime12 min readLesson 3 of 4
3.1 The object a member function runs on
Every non-static member function has an implicit parameter: a pointer to the object it was called on, named this. Inside set_volume, this points to the object whose set_volume is running. Writing volume_ is shorthand for this->volume_.
this is useful when a parameter has the same name as a member, when a function returns the object for chaining, and when a member function passes the whole object to another function. In a const member function the type of this is a pointer to const, so the compiler blocks changes through it.
#include <iostream>
class Settings {
public:
Settings& set_width(int width) { width_ = width; return *this; }
Settings& set_height(int height) { height_ = height; return *this; }
void show() const { std::cout << width_ << "x" << height_ << "\n"; }
private:
int width_ = 80;
int height_ = 24;
};
int main() {
Settings settings;
settings.set_width(120).set_height(40);
settings.show();
return 0;
}Output
120x40
3.1.1 Chaining and disambiguation
A member function that returns *this by reference hands back the object it was called on. Calls can then be chained: config.set_width(80).set_height(24);. The return type is ClassName&, so each call works on the same object.
The same expression helps when a constructor parameter shadows a member. this->name = name; assigns the parameter to the member. A trailing underscore on member names avoids the shadowing in the first place, and this-> remains available for the cases where the two names must match.
#include <iostream>
#include <string>
class Player {
public:
Player(std::string name) { this->name = name; }
void rename(const std::string& name) { this->name = name; }
void show() const { std::cout << name << "\n"; }
private:
std::string name;
};
int main() {
Player player("Ravi");
player.rename("Mei");
player.show();
return 0;
}Output
Mei
3.1.2 Static data members
A static data member belongs to the class as a whole. The class holds one copy, and it exists even when no object has been created. A count of live objects is the usual first example.
Inside the class the member is declared with static. Before C++17 you also define it once outside the class, usually in a source file: int Connection::live_ = 0;. C++17 allows inline static int live_ = 0; inside the class, which avoids the separate definition. Access it through the class name, as in Connection::live().
#include <iostream>
class Connection {
public:
Connection() { ++live_; }
~Connection() { --live_; }
static int live() { return live_; }
private:
static int live_;
};
int Connection::live_ = 0;
int main() {
Connection first;
{ Connection second; std::cout << "Inside: " << Connection::live() << "\n"; }
std::cout << "After: " << Connection::live() << "\n";
return 0;
}Output
Inside: 2 After: 1
3.1.3 Static member functions
A static member function has no this pointer, so it cannot read or write non-static members. It can use static members, its parameters, and its locals. Call it with the class name: Token::create(21).
Static functions are a common place for factories. A factory hides the constructor, validates arguments, and returns a fully formed object. The constructor stays private, and the static function becomes the only way to create one. A new validation rule then goes into the factory and applies to every caller.
#include <iostream>
class Token {
public:
static Token create(int seed) { return Token(seed * 2); }
int value() const { return value_; }
private:
explicit Token(int value) : value_(value) {}
int value_;
};
int main() {
Token token = Token::create(21);
std::cout << "Token: " << token.value() << "\n";
return 0;
}Output
Token: 42
3.1.4 Static locals and static members
A local variable declared static inside a function is created once and keeps its value between calls. Its lifetime runs until the program ends. A static data member also lives for the whole program, and the class owns it, while a static local belongs to its function.
Both are shared state. Shared state makes tests depend on call order, so use it for counters and caches that genuinely belong to the class. Initialization of a function-local static is thread safe in C++11 and later, which is why it appears in lazy singleton code.
3.1.5 This in const member functions
A const member function treats the object as read-only. The type of this becomes const ClassName*, so a member assignment inside that function fails to compile. Calling a non-const member function from a const member function also fails, because that call would need a non-const pointer.
A const object can call only const member functions. A const reference to an object has the same restriction. This is why getters and display functions should be marked const: without the marker, they cannot be used on const objects or through const references.
3.1.6 Common mistakes
- Forgetting
staticin the class declaration but using it in the out-of-class definition, or the reverse. - Declaring a static data member and never defining it, which produces a linker error.
- Writing
return this;when the function promises to return an object reference. The correct expression isreturn *this;. - Using a static member for data that should be per object.
- Calling a non-static member from a static function.
- Returning
*thisby value from a chaining function, which copies the object and breaks the chain.
3.1.7 Practice
- Add a
set_labelfunction that returnsSettings&and chain it with the existing setters. - Write a class with a static counter that increases in the constructor and decreases in the destructor.
- Add a static factory that rejects a negative argument and returns a valid object.
- Mark a getter as
const, call it on a const object, and then try removingconstto see the error. - Explain why a static member function cannot use
this.
