[C++] 항목 9: 객체 생성 및 소멸 과정 중에는 절대로 가상 함수를 호출하지 말자!

업데이트:     Updated:

카테고리:

태그:

이 글은 아래의 책을 자세히 정리한 후, 정리한 글을 GPT에게 요약을 요청하여 작성되었습니다.
이펙티브 C++ 제3판, 스콧 마이어스 저자, 곽용재 번역

📦 2. 생성자, 소멸자 및 대입 연산자

👉🏻 항목 9: 객체 생성 및 소멸 과정 중에는 절대로 가상 함수를 호출하지 말자!

1. 생성자와 소멸자에서 가상 함수 호출?

class Transaction {
public:
    Transaction() {
        logTransaction(); // ❌ 가상 함수 호출
    }
    virtual void logTransaction() const = 0;
};

class BuyTransaction : public Transaction {
public:
    virtual void logTransaction() const override {
        // 거래 로깅 구현
    }
};

BuyTransaction b;

위 코드에서 Transaction의 생성자에서 호출된 logTransaction()파생 클래스(BuyTransaction)의 것이 아닌 기본 클래스(Transaction)의 것이 호출된다.

그 이유는?


2. 생성자는 기본 클래스 → 파생 클래스 순서로 호출된다

  • 객체 생성 시, 기본 클래스 생성자가 먼저 호출된다.
  • 이 시점엔 아직 파생 클래스 부분은 초기화되지 않은 상태이다.
  • 따라서 기본 클래스 생성자 내부에서 호출된 가상 함수는 기본 클래스의 버전으로 동작한다.
  • 마찬가지로 소멸 시에는 파생 클래스 → 기본 클래스 순서로 소멸되기 때문에, 소멸자에서 가상 함수를 호출해도 기본 클래스 버전이 호출된다.
class Transaction {
public:
    Transaction() {
        init(); // ❌ 여기서 가상 함수 간접 호출
    }
    virtual void logTransaction() const = 0;

private:
    void init() {
        logTransaction(); // ❌ 간접 호출이지만 여전히 위험
    }
};
  • 비가상 함수인 init()을 통해 가상 함수가 호출되는 경우도 있으므로, 주의가 필요하다.
  • 이처럼 “알면서도 실수”가 아닌 “모르고도 실수”할 수 있다.

3. 위험한 이유는?

  • 생성자 시점에 파생 클래스 멤버는 아직 초기화되지 않음
  • 그 상태에서 파생 클래스의 가상 함수가 호출된다면? → 정의되지 않은 동작(UB) 발생
  • 심각한 경우, 비어있는 포인터 접근이나 프로그램 종료로 이어질 수 있음

✅ 해결 방법

가상 함수를 호출하는 대신, 필요한 정보를 생성자 매개변수로 전달하자!

class Transaction {
public:
    explicit Transaction(const string& logInfo) {
        logTransaction(logInfo); // ✅ 비가상 함수 호출
    }

    void logTransaction(const string& logInfo) const {
        // 로그 처리
    }
};

class BuyTransaction : public Transaction {
public:
    BuyTransaction(*parameters*)
        : Transaction(createLogString(*parameters*)) {
        ...
    }

private:
    static string createLogString(*parameters*);
};
  • logTransaction()비가상 함수로 만든다
  • 파생 클래스에서는 정적 함수로 로그 문자열을 만든 뒤, 기본 클래스 생성자에 전달
  • 이렇게 하면 가상 함수 호출 없이 로그 기록 가능
  • 정적 함수는 객체 상태를 필요로 하지 않기 때문에 미초기화 상태 문제 회피

🧐 정리

  1. 생성자와 소멸자에서는 가상 함수를 절대로 호출하지 말자.
  2. 생성자는 기본 클래스 → 파생 클래스, 소멸자는 그 반대 순서로 동작한다.
  3. 생성자/소멸자 내 가상 함수 호출은 의도치 않은 기본 클래스 함수 호출로 이어진다.
  4. 필요한 동작은 비가상 함수 + 생성자 매개변수를 활용해서 처리하자.

Cpp 카테고리 내 다른 글 보러가기

댓글남기기