[C++] 항목 9: 객체 생성 및 소멸 과정 중에는 절대로 가상 함수를 호출하지 말자!
카테고리: Cpp
태그: Cpp
이 글은 아래의 책을 자세히 정리한 후, 정리한 글을 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()
을 비가상 함수로 만든다- 파생 클래스에서는 정적 함수로 로그 문자열을 만든 뒤, 기본 클래스 생성자에 전달
- 이렇게 하면 가상 함수 호출 없이 로그 기록 가능
- 정적 함수는 객체 상태를 필요로 하지 않기 때문에 미초기화 상태 문제 회피
🧐 정리
- 생성자와 소멸자에서는 가상 함수를 절대로 호출하지 말자.
- 생성자는 기본 클래스 → 파생 클래스, 소멸자는 그 반대 순서로 동작한다.
- 생성자/소멸자 내 가상 함수 호출은 의도치 않은 기본 클래스 함수 호출로 이어진다.
- 필요한 동작은 비가상 함수 + 생성자 매개변수를 활용해서 처리하자.
댓글남기기