C++ this Pointer: Chaining, const Methods, Lambdas, and CRTP
이 글의 핵심
The implicit this pointer in non-static member functions: disambiguation, method chaining, self-assignment checks, lambda captures, and const/ref qualifiers. Complete guide with practical examples.
What is this?
In any non-static member function, this is an implicit pointer to the object the method was called on. The compiler passes it automatically — you don’t declare it, but you can use it:
class Counter {
int count_;
public:
Counter() : count_(0) {}
void increment() {
count_++; // implicit: this->count_++
}
int value() const {
return count_; // implicit: return this->count_
}
};
Counter c;
c.increment(); // compiler calls: increment(&c) — passes &c as this
Most of the time you don’t need this explicitly — the compiler finds members automatically. You need this when:
- A parameter has the same name as a member (disambiguation)
- You want to return the current object (
return *this) - You need to check for self-assignment (
this == &other) - You’re passing the object to a callback or storing a pointer to it
Disambiguation: Parameter vs Member
When a constructor or setter parameter shares a name with a member, this-> distinguishes them:
class Rectangle {
int width_;
int height_;
public:
// Parameter 'width' shadows member 'width_' — no conflict here
Rectangle(int width, int height)
: width_(width), height_(height) {} // initializer list preferred
// If you wrote the assignment manually:
void setSize(int width, int height) {
this->width_ = width; // explicit: left is member, right is parameter
this->height_ = height;
}
};
Initializer lists (: width_(width)) are the preferred way to initialize members in constructors — they avoid this-> entirely and are more efficient.
The Type of this
this changes type depending on the qualifiers of the member function:
class Widget {
int data_ = 0;
public:
void modify() {
// this is: Widget* (non-const, can modify members)
data_ = 42;
}
void read() const {
// this is: const Widget* (cannot modify members)
// data_ = 42; // compile error
}
void refQualified() & {
// this is an lvalue Widget — called on lvalue objects
}
void refQualified() && {
// this is an rvalue Widget — called on temporaries
}
};
Widget w;
w.modify(); // OK
w.read(); // OK
const Widget cw;
// cw.modify(); // compile error — modify() is not const
cw.read(); // OK
Method Chaining with return *this
Returning *this by reference allows calling multiple methods on the same line:
#include <string>
#include <iostream>
class QueryBuilder {
std::string query_;
std::vector<std::string> conditions_;
int limit_ = -1;
public:
QueryBuilder& from(const std::string& table) {
query_ = "SELECT * FROM " + table;
return *this;
}
QueryBuilder& where(const std::string& condition) {
conditions_.push_back(condition);
return *this;
}
QueryBuilder& limit(int n) {
limit_ = n;
return *this;
}
std::string build() const {
std::string sql = query_;
for (size_t i = 0; i < conditions_.size(); i++) {
sql += (i == 0 ? " WHERE " : " AND ") + conditions_[i];
}
if (limit_ > 0) sql += " LIMIT " + std::to_string(limit_);
return sql;
}
};
int main() {
auto sql = QueryBuilder{}
.from("users")
.where("age > 18")
.where("active = true")
.limit(10)
.build();
std::cout << sql << '\n';
// SELECT * FROM users WHERE age > 18 AND active = true LIMIT 10
}
The fluent builder pattern is one of the most common uses of return *this.
Self-Assignment Check in operator=
When implementing a copy assignment operator, check for self-assignment first using this:
class Buffer {
char* data_;
size_t size_;
public:
Buffer(size_t n) : data_(new char[n]), size_(n) {}
~Buffer() { delete[] data_; }
Buffer& operator=(const Buffer& other) {
if (this == &other) return *this; // guard against self-assignment
// Would be unsafe without the guard:
// delete[] data_; // frees other.data_ if this == &other
// data_ = new char[other.size_]; // new allocation
// std::copy(other.data_, ...); // reads freed memory!
delete[] data_;
size_ = other.size_;
data_ = new char[size_];
std::copy(other.data_, other.data_ + size_, data_);
return *this;
}
};
Self-assignment happens more often than you’d expect — a = a is the obvious case, but v[i] = v[j] when i == j is a subtle one.
Passing this to Callbacks
When registering an object as a callback, you pass this to store a pointer to the current instance:
#include <functional>
#include <iostream>
class Button {
std::function<void()> onClick_;
public:
void setCallback(std::function<void()> cb) {
onClick_ = cb;
}
void click() {
if (onClick_) onClick_();
}
};
class App {
int clickCount_ = 0;
Button button_;
public:
App() {
// Pass this to capture App in the lambda
button_.setCallback([this]() {
clickCount_++;
std::cout << "Clicked " << clickCount_ << " times\n";
});
}
void run() {
button_.click();
button_.click();
button_.click();
}
};
int main() {
App app;
app.run();
// Clicked 1 times
// Clicked 2 times
// Clicked 3 times
}
Lifetime warning: the lambda captures a raw pointer (this). If the App object is destroyed before the Button callback is triggered (e.g., in async code), the lambda will call a dangling pointer.
Lambdas and this
class EventHandler {
std::string name_;
std::function<void()> handler_;
public:
EventHandler(const std::string& name) : name_(name) {
// [this] — captures pointer; safe only if lambda won't outlive the object
handler_ = [this]() {
std::cout << "Event from: " << name_ << '\n';
};
// [*this] (C++17) — copies the object into the lambda
// Safe even if EventHandler is destroyed, but copies all members
auto safeHandler = [*this]() {
std::cout << "Event from: " << name_ << '\n';
};
}
void trigger() { handler_(); }
};
Use [this] when you know the lambda’s lifetime is bounded by the object’s lifetime.
Use [*this] when the lambda might outlive the object (async callbacks, stored futures) — but beware the copy cost for large objects.
Static Member Functions Have No this
Static member functions belong to the class, not any instance. They have no this:
class Config {
static Config* instance_;
int port_ = 8080;
Config() = default;
public:
// Static factory — no this, no instance needed
static Config& get() {
if (!instance_) instance_ = new Config();
return *instance_;
}
// Non-static — has this, accesses instance data
int port() const { return port_; }
};
Config* Config::instance_ = nullptr;
int main() {
Config::get().port(); // static function returns instance; then non-static call
}
If you call a non-static method through a null pointer, it’s undefined behavior — even if the method doesn’t actually access members, the behavior is still technically UB.
this in Derived Classes and CRTP
In derived classes, this has the type of the derived class in methods defined there. CRTP (Curiously Recurring Template Pattern) uses static_cast<Derived*>(this) to call derived class methods from a base class template:
template<typename Derived>
class Serializable {
public:
std::string serialize() const {
// Cast this to Derived* to call derived class method
const Derived& d = static_cast<const Derived&>(*this);
return d.toJson(); // calls Derived::toJson()
}
};
class User : public Serializable<User> {
std::string name_;
int age_;
public:
User(std::string name, int age) : name_(std::move(name)), age_(age) {}
std::string toJson() const {
return "{\"name\":\"" + name_ + "\",\"age\":" + std::to_string(age_) + "}";
}
};
int main() {
User u("Alice", 30);
std::cout << u.serialize() << '\n';
// {"name":"Alice","age":30}
}
The static_cast is safe here because Serializable<User> is always used as a base of User — the Derived object is always fully constructed by the time serialize() is called.
Key Takeaways
thisis an implicit pointer to the current object in non-static member functions — always available, usually not needed explicitly- Disambiguation: use
this->memberwhen a parameter has the same name as a member return *thisenables method chaining (builder pattern, fluent APIs)- Self-assignment check:
if (this == &other) return *this;in copy assignment operators - In
constmethods,thisisconst T*— you cannot modify members - Static member functions have no
this— they belong to the class, not an instance [this]lambda capture: holds a raw pointer — dangling if the lambda outlives the object[*this](C++17): copies the object — safe for async but expensive for large objects
Frequently Asked Questions (FAQ)
Q. When would I use this in practice?
A. The implicit this pointer in non-static member functions: disambiguation, method chaining, self-assignment checks.
Q. What should I read before this?
A. Follow the previous article or related articles links at the bottom of each post to learn in sequence. See the C++ series index for the full picture.
Q. Where can I study this more deeply?
A. Check cppreference and the relevant library’s official documentation. The reference links at the end of the article are also worth using.
Related Articles (Internal Links)
Other articles related to this topic.
- C++ Classes and Objects: Constructors, Access Control, and
- C++ Functions: Parameters, Return Values, Overloading, and
- C++ Copy Initialization: The = Form, explicit, and Copy
Keywords Covered in This Article (Related Search Terms)
This article covers C++, this, pointer, member, class.