this 포인터
this 포인터
this는 멤버 함수 안에서 "지금 이 객체 자신"을 가리켜야 할 때 쓰는 키워드예요. 멤버 함수가 어떤 객체에 대해 호출됐는지를 알려주는 특별한 포인터라고 생각하면 돼요. this는 클래스 안의 특이한 키워드라서, 언제 어디서 쓸 수 있는지 규칙을 정확히 짚는 게 중요해요.
출처: cppreference
본문
문법 (Syntax)
this 표현식은 prvalue 표현식으로, 그 값은 **암시적 객체 매개변수(implicit object parameter)**의 주소예요. 즉 지금 호출되고 있는 암시적 객체 멤버 함수가 작동하는 대상 객체의 주소죠. this는 다음과 같은 문맥에서 나타날 수 있어요.
- 어떤 암시적 객체 멤버 함수의 몸통 안 — 멤버 초기화 목록(member initializer list)과 람다 표현식의 몸통을 포함해요. (C++11부터)
- 어떤 암시적 객체 멤버 함수의 선언에서 (선택적) cv 한정자 시퀀스 뒤의 어디든 — 예외 명세(exception specification)와 후행 반환 타입(trailing return type)을 포함해요. (C++11부터)
- 기본 멤버 초기화(default member initializer) 안.
- 람다 표현식의 캡처 목록(capture list) 안. (C++11부터)
설명 (Explanation)
this는 그 등장 지점의 **가장 안쪽에 있는 둘러싸는 클래스(innermost enclosing class)**하고만 연관돼요. 심지어 그 등장이 그 문맥에서 무효여도 그렇지요.
class Outer
{
int a[sizeof(*this)]; // Error: 멤버 함수 안이 아님
unsigned int sz = sizeof(*this); // OK: 기본 멤버 초기화 안
void f()
{
int b[sizeof(*this)]; // OK
struct Inner
{
int c[sizeof(*this)]; // Error: Inner의 멤버 함수 안이 아님
// "this"는 Outer와 연관되지 않음
// 비록 Outer의 멤버 함수 안에 있어도
};
}
};
클래스 X의 멤버 함수에서 this의 타입은 X*(X를 가리키는 포인터)예요. 멤버 함수가 cv 한정자 시퀀스 cv로 선언됐다면 this의 타입은 cv X*(동일하게 cv 한정된 X를 가리키는 포인터)가 되요. 생성자와 소멸자는 cv 한정자로 선언될 수 없으므로, 그 안에서 this의 타입은 const 객체를 만들거나 파괴할 때조차 항상 X*예요.
클래스 템플릿 안에서 this는 **의존 표현식(dependent expression)**이에요. 그래서 명시적으로 this->를 쓰면 다른 표현식을 의존 표현식으로 강제할 수 있어요.
template<typename T>
struct B
{
int var;
};
template<typename T>
struct D : B<T>
{
D()
{
// var = 1; // Error: "var"이 이 스코프에 선언되지 않음
this->var = 1; // OK
}
};
객체를 생성하는 동안, 생성자의 this 포인터로부터 직간접적으로 얻어지지 않은 glvalue를 통해 그 객체나 그 하위 객체의 값에 접근하면, 그렇게 얻은 값은 불특정(unspecified)이 돼요. 다시 말해 생성자 안에서 this 포인터는 앨리어싱될 수 없어요.
extern struct D d;
struct D
{
D(int a) : a(a), b(d.a) {} // b(a)나 b(this->a)가 올바른 표현
int a, b;
};
D d = D(1); // b(d.a)가 this를 거치지 않았으므로 d.b는 이제 불특정
delete this;를 실행하는 것도 가능해요. 단 그 객체가 new로 할당됐음을 프로그램이 보장할 수 있을 때만이에요. 다만 이렇게 하면 해제된 객체를 가리키는 모든 포인터가 무효가 되는데, this 포인터 자신도 포함돼요. delete this;가 반환된 뒤에는 그 멤버 함수가 클래스의 멤버를 참조할 수 없고(this의 암시적 역참조를 수반하므로), 다른 멤버 함수도 호출될 수 없어요.
이 패턴은 참조 카운트 기반 포인터(예: std::shared_ptr, C++11부터)에서 관리 객체의 마지막 참조가 스코프를 벗어날 때 참조 카운트를 줄이는 멤버 함수에 쓸 수 있어요.
class ref
{
// ...
void incRef() { ++mnRef; }
void decRef() { if (--mnRef == 0) delete this; }
};
키워드 (Keywords)
this
예제 (Example)
this를 쓰는 전형적인 장면들을 한 번에 보여주는 코드예요.
class T
{
int x;
void foo()
{
x = 6; // this->x = 6; 과 같음
this->x = 5; // this->의 명시적 사용
}
void foo() const
{
// x = 7; // Error: *this는 const
}
void foo(int x) // 매개변수 x가 같은 이름의 멤버를 가림
{
this->x = x; // 정규화되지 않은 x는 매개변수를 가리킴
// 구분을 위해 "this->"가 필요함
}
int y;
T(int x) : x(x), // 매개변수 x로 멤버 x를 초기화
y(this->x) // 멤버 x로 멤버 y를 초기화
{}
T& operator=(const T& b)
{
x = b.x;
return *this; // 많은 오버로드된 연산자가 *this를 반환
}
};
코드에서 핵심은 두 가지예요. 첫째, 매개변수가 멤버와 같은 이름이면 this->로 명확히 구분해줘야 한다는 점이고, 둘째, 많은 연산자처럼 객체 자신을 돌려줄 때 return *this;를 쓴다는 점이에요.
결함 보고 (Defect reports)
다음의 동작 변경 결함 보고가 이전에 공개된 C++ 표준들에 소급 적용됐어요.
| DR | 적용 | 공개된 동작 | 올바른 동작 |
|---|---|---|---|
| CWG 760 | C++98 | 중첩 클래스에서 this를 쓸 때 그것이 중첩 클래스와 연관되는지 둘러싸는 클래스와 연관되는지가 불특정이었음 |
this는 항상 가장 안쪽 중첩 클래스와 연관됨, 비정적 멤버 함수 안에 있는지와 무관하게 |
| CWG 2271 | C++98 | non-const 객체를 생성할 때 this가 앨리어싱될 수 있었음 |
이 경우에도 앨리어싱이 금지됨 |
| CWG 2869 | C++98 | 연관되지 않은 클래스의 정적 멤버 함수에서 this를 쓸 수 있는지가 불명확했음 |
명확히 함 |
더 알아보기
- 클래스:
this가 존재하는 이유가 되는 멤버 함수와 객체 개념. - 생성자:
this를 초기화 목록에서 쓰는 예. - 람다 표현식 (C++11): 캡처 목록에서
this를 쓸 수 있어요.