C++ this Pointer | "this 포인터" 가이드

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의 타입은 대략 다음 규칙을 따릅니다.

  • 일반 멤버 함수: thisX* (또는 클래스에 따라 X const* 등으로 조정된 포인터).
  • const 멤버 함수: thisconst X* — 멤버를 통한 수정이 금지됩니다.
  • volatile 멤버 함수: thisvolatile 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를 등록할 때, 소멸자에서 반드시 해제·역등록해 이중 등록이나 댕글링을 막습니다.

흔한 실수

  1. 비동기·콜백 후 수명 종료: [this] 람다가 큐에만 남고 객체는 이미 파괴된 경우.
  2. 자기 대입 검사 누락: 이동 대입에서 this == &other가 아닌 주소 동일성으로 체크해야 합니다(대부분 동일).
  3. 정적 멤버에서 this 사용 시도: 정적 함수에는 this가 없습니다.
  4. 생성자/소멸자에서의 가상 호출: this로 가상 함수를 호출해도 생성/소멸 중에는 규칙이 제한됩니다(Effective C++ 등에서 다루는 주제).
  5. 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 멤버 함수에서 thisconst 포인터(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:

관련 글: 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 |