C++ this Pointer | "this 포인터" 가이드
이 글의 핵심
C++ this Pointer에 대한 실전 가이드입니다.
this 포인터란?
this 는 현재 객체를 가리키는 포인터입니다. 멤버 함수 내부에서 자동으로 제공되며, 객체 자신을 참조할 때 사용합니다. this는 암시적으로 전달되며, 명시적으로 사용할 수 있습니다.
class MyClass {
int value;
public:
void setValue(int value) {
this->value = value; // 멤버 변수
}
};
왜 필요한가?:
- 이름 충돌 해결: 멤버 변수와 매개변수 이름이 같을 때
- 메서드 체이닝: 자신을 반환하여 연속 호출
- 자기 대입 방지: 대입 연산자에서 자신과의 비교
- 콜백 등록: 자신을 다른 함수에 전달
// ❌ 이름 충돌: 모호함
class MyClass {
int value;
public:
void setValue(int value) {
value = value; // 매개변수 = 매개변수
}
};
// ✅ this 사용: 명확함
class MyClass {
int value;
public:
void setValue(int value) {
this->value = value; // 멤버 = 매개변수
}
};
this의 타입:
this의 타입은 멤버 함수의 cv-한정자(const, volatile)에 따라 달라집니다.
class MyClass {
public:
void func() {
// this: MyClass*
}
void constFunc() const {
// this: const MyClass*
}
void volatileFunc() volatile {
// this: volatile MyClass*
}
};
this의 동작 원리:
멤버 함수는 내부적으로 this 포인터를 첫 번째 매개변수로 받습니다.
class MyClass {
int value;
public:
void setValue(int v) {
this->value = v;
}
};
// 개념적으로 다음과 같이 변환됨:
void setValue(MyClass* this, int v) {
this->value = v;
}
// 호출
MyClass obj;
obj.setValue(10); // setValue(&obj, 10)
기본 사용
class Point {
int x, y;
public:
Point(int x, int y) {
this->x = x; // this-> 명시
this->y = y;
}
void print() {
std::cout << "(" << this->x << ", " << this->y << ")";
}
};
실전 예시
예시 1: 메서드 체이닝
class Builder {
std::string data;
public:
Builder& append(const std::string& s) {
data += s;
return *this; // 자신 반환
}
Builder& clear() {
data.clear();
return *this;
}
std::string build() const {
return data;
}
};
int main() {
Builder b;
auto result = b.append("Hello")
.append(" ")
.append("World")
.build();
}
예시 2: 자기 대입 방지
class Array {
int* data;
size_t size;
public:
Array& operator=(const Array& other) {
if (this != &other) { // 자기 대입 체크
delete[] data;
size = other.size;
data = new int[size];
std::copy(other.data, other.data + size, data);
}
return *this;
}
};
예시 3: 콜백 등록
class Button {
public:
void onClick() {
if (callback) {
callback(this); // 자신을 전달
}
}
void setCallback(void (*cb)(Button*)) {
callback = cb;
}
private:
void (*callback)(Button*) = nullptr;
};
예시 4: const 멤버 함수
class Data {
int value;
public:
void setValue(int v) {
this->value = v; // OK
}
int getValue() const {
// this는 const Data*
// this->value = 10; // 에러
return this->value; // OK
}
};
this 타입
class MyClass {
public:
void func() {
// this: MyClass*
}
void constFunc() const {
// this: const MyClass*
}
void volatileFunc() volatile {
// this: volatile MyClass*
}
};
자주 발생하는 문제
문제 1: 이름 충돌
class MyClass {
int value;
public:
// ❌ 모호함
void setValue(int value) {
value = value; // 매개변수 = 매개변수
}
// ✅ this 사용
void setValue(int value) {
this->value = value;
}
};
문제 2: 정적 멤버
class MyClass {
static int count;
public:
static void increment() {
// this->count++; // 에러: this 없음
count++; // OK
}
};
문제 3: 람다 캡처
class MyClass {
int value = 10;
public:
void func() {
auto lambda = [this]() {
std::cout << this->value; // OK
};
lambda();
}
};
문제 4: 생성자에서 this
class MyClass {
public:
MyClass() {
// this 사용 가능하지만 주의
// 가상 함수 호출은 다형성 안됨
}
};
this 활용
// 1. 체이닝
return *this;
// 2. 자기 대입 방지
if (this != &other) {}
// 3. 콜백
callback(this);
// 4. 이름 충돌 해결
this->member = parameter;
실무 패턴
패턴 1: 빌더 패턴
class HttpRequest {
std::string url_;
std::string method_ = "GET";
std::map<std::string, std::string> headers_;
public:
HttpRequest& setUrl(const std::string& url) {
url_ = url;
return *this;
}
HttpRequest& setMethod(const std::string& method) {
method_ = method;
return *this;
}
HttpRequest& addHeader(const std::string& key, const std::string& value) {
headers_[key] = value;
return *this;
}
void send() {
std::cout << method_ << " " << url_ << '\n';
for (const auto& [key, value] : headers_) {
std::cout << key << ": " << value << '\n';
}
}
};
// 사용
HttpRequest()
.setUrl("https://api.example.com")
.setMethod("POST")
.addHeader("Content-Type", "application/json")
.addHeader("Authorization", "Bearer token")
.send();
패턴 2: 싱글톤
class Logger {
private:
Logger() = default;
public:
static Logger& getInstance() {
static Logger instance;
return instance;
}
Logger& log(const std::string& message) {
std::cout << "[LOG] " << message << '\n';
return *this;
}
Logger& error(const std::string& message) {
std::cerr << "[ERROR] " << message << '\n';
return *this;
}
};
// 사용
Logger::getInstance()
.log("Application started")
.log("Processing data")
.error("An error occurred");
패턴 3: RAII 래퍼
class ScopedLock {
std::mutex& mutex_;
public:
ScopedLock(std::mutex& m) : mutex_(m) {
mutex_.lock();
}
~ScopedLock() {
mutex_.unlock();
}
// 이동 생성자
ScopedLock(ScopedLock&& other) noexcept : mutex_(other.mutex_) {
// other는 더 이상 소유하지 않음
}
// 복사 금지
ScopedLock(const ScopedLock&) = delete;
ScopedLock& operator=(const ScopedLock&) = delete;
};
this의 타입과 const·ref 한정 심화
멤버 함수에 붙는 cv 한정자(const, volatile)와 ref 한정자(&, &&)는 this의 타입을 바꿉니다. 비-정적 멤버 함수 안에서 this의 타입은 대략 다음 규칙을 따릅니다.
- 일반 멤버 함수:
this는X*(또는 클래스에 따라X const*등으로 조정된 포인터). const멤버 함수:this는const X*— 멤버를 통한 수정이 금지됩니다.volatile멤버 함수:this는volatile X*.- C++11 이후 ref 한정 멤버 함수:
void foo() &에서는this가 lvalue 객체를,void foo() &&에서는 rvalue(std::move로 넘긴 임시 등) 객체를 가리키는 맥락에서 오버로드 분리에 쓰입니다.
struct S {
void work() & { /* *this는 lvalue */ }
void work() && { /* *this는 rvalue: 이동 최적화·체이닝에 활용 */ }
};
const 멤버 안에서는 mutable로 표시한 멤버만 예외적으로 수정할 수 있습니다. this 자체는 상수 포인터(T* const 형태)이므로 다른 객체를 가리키도록 재대입할 수는 없습니다.
람다 캡처에서의 this
[this]: 현재 객체의 주소를 복사해 둡니다. 람다가 객체 수명보다 오래 살아가면 댕글링이 됩니다(비동기 콜백, 타이머, 스레드 큐에 특히 주의).[*this](C++17): 객체를 값으로 복사해 캡처합니다. 짧은 수명 객체를 넘길 때는 복사 비용과 예외 안전성을 함께 고려하세요.[=]: 클래스 멤버 함수 안에서[=]는 암시적으로this를 캡처하는 것과 유사하게 동작할 수 있어(구현·경고에 따라 다름) 명시적으로[this]또는[*this]를 쓰는 편이 의도를 드러내기 좋습니다.
void Widget::postToQueue(TaskQueue& q) {
q.enqueue([this] {
doWork(); // 큐에 남은 동안 Widget이 파괴되면 미정의 동작
});
}
수명을 보장하려면 shared_from_this 패턴, 작업 취소 토큰, 또는 [*this]로 값 복사 후 필요 시 약한 참조 등을 검토하세요.
명시적 this와 암묵적 멤버 접근
같은 스코프에서 멤버 이름을 쓰면 컴파일러는 this-> 없이도 멤버를 찾습니다. 즉 value는 (로컬에 가리는 이름이 없으면) this->value와 같습니다.
- 암묵적: 짧고 단순한 멤버 접근에 읽기 쉽습니다.
- 명시적
this->: 템플릿 기반 클래스에서 의존 이름(dependent name) 해석, 매개변수와의 이름 충돌, 코드 리뷰 시 “멤버다”라는 점을 드러낼 때 유리합니다.
template<typename T>
struct Base { T x; };
template<typename T>
struct Derived : Base<T> {
void f() {
// this->x 또는 Base<T>::x — 템플릿에서는 this->가 종종 필요
this->x = {};
}
};
추가 실전 패턴
- CRTP: 기반 클래스 템플릿에서
static_cast<Derived*>(this)로 파생 쪽 구현을 호출할 때this를 넘깁니다. - 비교 연산자:
return *this < rhs처럼 일관된 표현을 유지할 때this를 직접 쓰기보다는 멤버 비교로 끝내는 경우가 많지만, 식별자 비교가 필요하면this와&rhs를 사용합니다. - 컨테이너에 객체 포인터 저장:
std::vector<Observer*>에this를 등록할 때, 소멸자에서 반드시 해제·역등록해 이중 등록이나 댕글링을 막습니다.
흔한 실수
- 비동기·콜백 후 수명 종료:
[this]람다가 큐에만 남고 객체는 이미 파괴된 경우. - 자기 대입 검사 누락: 이동 대입에서
this == &other가 아닌 주소 동일성으로 체크해야 합니다(대부분 동일). - 정적 멤버에서
this사용 시도: 정적 함수에는this가 없습니다. - 생성자/소멸자에서의 가상 호출:
this로 가상 함수를 호출해도 생성/소멸 중에는 규칙이 제한됩니다(Effective C++ 등에서 다루는 주제). nullptr인 것처럼 가정: 정상적인 멤버 호출에서this는 null이 아니라고 가정하는 것이 일반적이며, 비정상 호출은 미정의 동작입니다.
FAQ
Q1: this는 무엇인가요?
A: this는 현재 객체를 가리키는 포인터입니다. 멤버 함수 내부에서 자동으로 제공되며, 객체 자신을 참조할 때 사용합니다.
class MyClass {
int value;
public:
void setValue(int value) {
this->value = value; // this는 현재 객체
}
};
Q2: this는 언제 사용해야 하나요?
A:
- 이름 충돌: 멤버 변수와 매개변수 이름이 같을 때
- 메서드 체이닝: 자신을 반환하여 연속 호출
- 자기 대입 방지: 대입 연산자에서 자신과의 비교
- 콜백 등록: 자신을 다른 함수에 전달
// 이름 충돌
void setValue(int value) {
this->value = value;
}
// 메서드 체이닝
Builder& append(const std::string& s) {
data += s;
return *this;
}
// 자기 대입 방지
Array& operator=(const Array& other) {
if (this != &other) {
// 복사
}
return *this;
}
Q3: 정적 멤버 함수에서 this를 사용할 수 있나요?
A: 아니요. 정적 멤버 함수는 객체 없이 호출되므로 this 포인터가 없습니다.
class MyClass {
static int count;
public:
static void increment() {
// this->count++; // 에러: this 없음
count++; // OK
}
};
Q4: const 멤버 함수에서 this는 어떻게 되나요?
A: const 멤버 함수에서 this는 const 포인터(const MyClass*)가 됩니다. 따라서 멤버 변수를 수정할 수 없습니다.
class Data {
int value;
public:
void setValue(int v) {
this->value = v; // OK: this는 Data*
}
int getValue() const {
// this는 const Data*
// this->value = 10; // 에러: const 객체 수정 불가
return this->value; // OK: 읽기만
}
};
Q5: 람다에서 this를 캡처하려면?
A: [this] 또는 [=]로 캡처합니다. C++17부터는 [*this]로 객체를 복사 캡처할 수 있습니다.
class MyClass {
int value = 10;
public:
void func() {
// [this]: this 포인터 캡처
auto lambda1 = [this]() {
std::cout << this->value << '\n';
};
// [*this]: 객체 복사 캡처 (C++17)
auto lambda2 = [*this]() mutable {
value++; // 복사본 수정
};
lambda1();
lambda2();
}
};
Q6: 생성자나 소멸자에서 this를 사용할 수 있나요?
A: 가능하지만 주의해야 합니다. 생성자에서 가상 함수를 호출하면 다형성이 작동하지 않으며, 소멸자에서는 이미 파생 클래스 부분이 소멸된 상태입니다.
class Base {
public:
Base() {
// this 사용 가능
// 하지만 가상 함수 호출은 Base 버전만 호출됨
}
virtual void init() {
std::cout << "Base::init\n";
}
};
class Derived : public Base {
public:
Derived() {
// Base 생성자에서 init() 호출 시 Base::init만 호출됨
}
void init() override {
std::cout << "Derived::init\n";
}
};
Q7: this 학습 리소스는?
A:
- “C++ Primer” (5th Edition) by Stanley Lippman
- “Effective C++” (3rd Edition) by Scott Meyers
- cppreference.com - this pointer
관련 글: Lambda Capture, Method Chaining.
한 줄 요약: this는 현재 객체를 가리키는 포인터로, 멤버 함수 내부에서 객체 자신을 참조할 때 사용합니다.
같이 보면 좋은 글 (내부 링크)
이 주제와 연결되는 다른 글입니다.
- C++ static 멤버 | “Static Members” 가이드
- C++ mutable Keyword | “mutable 키워드” 가이드
- C++ nullptr | “널 포인터” 가이드
관련 글
- C++ static 멤버 |
- C++ struct vs class |
- C++ Initialization Order 완벽 가이드 | 초기화 순서의 모든 것
- C++ mutable Keyword |
- C++ nullptr vs NULL |