본문으로 건너뛰기
Previous
Next
C++ this Pointer | 'this 포인터' 가이드

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

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

이 글의 핵심

C++ this Pointer - "this 포인터" 가이드. C++ this Pointer의 this 포인터란?, 기본 사용, 실전 예시를 실전 코드와 함께 설명합니다.

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]를 쓰는 편이 의도를 드러내기 좋습니다.

C/C++ 예제 코드입니다.

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++ this Pointer | ‘this 포인터’ 가이드」)를 구현·런타임·운영 관점에서 다시 압축합니다. 도메인별 세부 구현은 글마다 다르지만, 입력 검증 → 핵심 연산 → 부작용(I/O·네트워크·동시성) → 관측의 흐름으로 장애를 나누면 원인 추적이 빨라집니다.

내부 동작과 핵심 메커니즘

flowchart TD
  A[입력·요청·이벤트] --> B[파싱·검증·디코딩]
  B --> C[핵심 연산·상태 전이]
  C --> D[부작용: I/O·네트워크·동시성]
  D --> E[결과·관측·저장]
sequenceDiagram
  participant C as 클라이언트/호출자
  participant B as 경계(런타임·게이트웨이·프로세스)
  participant D as 의존성(API·DB·큐·파일)
  C->>B: 요청/이벤트
  B->>D: 조회·쓰기·RPC
  D-->>B: 지연·부분 실패·재시도 가능
  B-->>C: 응답 또는 오류(코드·상관 ID)
  • 불변 조건(Invariant): 버퍼 경계, 프로토콜 상태, 트랜잭션 격리, FD 상한 등 단계별로 문장으로 적어 두면 디버깅 비용이 줄어듭니다.
  • 결정성: 순수 층과 시간·네트워크·스케줄에 의존하는 층을 분리해야 테스트와 장애 분석이 쉬워집니다.
  • 경계 비용: 직렬화, 인코딩, syscall 횟수, 락 경합, 할당·GC, 캐시 미스를 의심 목록에 둡니다.
  • 백프레셔: 생산자가 소비자보다 빠를 때 버퍼·큐·스트림에서 속도를 줄이는 신호를 어디에 둘지 정의합니다.

프로덕션 운영 패턴

영역운영 관점 질문
관측성요청 단위 상관 ID, 에러율·지연 p95/p99, 의존성 타임아웃·재시도가 대시보드에 보이는가
안전성입력 검증·권한·비밀·감사 로그가 코드 경로마다 일관적인가
신뢰성재시도는 멱등 연산에만 적용되는가, 서킷 브레이커·백오프·DLQ가 있는가
성능캐시·배치 크기·커넥션 풀·인덱스·백프레셔가 데이터 규모에 맞는가
배포롤백 룬북, 카나리/블루그린, 마이그레이션·피처 플래그가 문서화되어 있는가
용량피크 트래픽·디스크·FD·스레드 풀 상한을 주기적으로 검증하는가

스테이징은 데이터 양·네트워크 RTT·동시성을 프로덕션에 가깝게 맞출수록 재현율이 올라갑니다.

확장 예시: 엔드투엔드 미니 시나리오

앞선 본문 주제(「C++ this Pointer | ‘this 포인터’ 가이드」)를 배포·운영 흐름에 맞춰 옮긴 체크리스트입니다. 도메인에 맞게 단계 이름만 바꿔 적용할 수 있습니다.

  1. 입력 계약 고정: 스키마·버전·최대 페이로드·타임아웃·에러 코드를 경계에 둔다.
  2. 핵심 경로 계측: 요청 ID, 단계별 지연, 외부 호출 결과 코드를 로그·메트릭·트레이스에서 한 흐름으로 본다.
  3. 실패 주입: 의존성 타임아웃·5xx·부분 데이터·락 대기를 스테이징에서 재현한다.
  4. 호환·롤백: 설정/마이그레이션/클라이언트 버전을 되돌릴 수 있는지 확인한다.
  5. 부하 후 검증: 피크 대비 p95/p99, 에러율, 리소스 상한, 알림 임계값을 점검한다.
handle(request):
  ctx = newCorrelationId()
  validated = validateSchema(request)
  authorize(validated, ctx)
  result = domainCore(validated)
  persistOrEmit(result, idempotentKey)
  recordMetrics(ctx, latency, outcome)
  return result

문제 해결(Troubleshooting)

증상가능 원인조치
간헐적 실패레이스, 타임아웃, 외부 의존성, DNS최소 재현 스크립트, 분산 트레이스·로그 상관관계, 재시도·서킷 설정 점검
성능 저하N+1, 동기 I/O, 락 경합, 과도한 직렬화, 캐시 미스프로파일러·APM으로 핫스팟 확인 후 한 가지씩 제거
메모리 증가캐시 무제한, 구독/리스너 누수, 대용량 버퍼, 커넥션 미반납상한·TTL·힙/FD 스냅샷 비교
빌드·배포만 실패환경 변수, 권한, 플랫폼 차이, lockfileCI 로그와 로컬 diff, 런타임·이미지 버전 핀
설정 불일치프로필·시크릿·기본값, 리전스키마 검증된 설정 단일 소스와 배포 매트릭스 표준화
데이터 불일치비멱등 재시도, 부분 쓰기, 캐시 무효화 누락멱등 키·아웃박스·트랜잭션 경계 재검토

권장 순서: (1) 최소 재현 (2) 최근 변경 범위 축소 (3) 환경·의존성 차이 (4) 관측으로 가설 검증 (5) 수정 후 회귀·부하 테스트.

배포 전에는 git addgit commitgit pushnpm run deploy 순서를 권장합니다.


이 글에서 다루는 키워드 (관련 검색어)

C++, this, pointer, member, class 등으로 검색하시면 이 글이 도움이 됩니다.