센로그
[EC++] 9. 객체 생성 및 소멸 과정 중에는 절대로 가상 함수를 호출하지 말자 본문
생성자나 소멸자에서 가상함수를 호출하면 안되는 이유
- 기본 클래스의 생성 과정에서는 가상 함수가 먹히지 않는다.
- 생성자는 기본 클래스 → 파생 클래스 순으로 호출된다.
- 이때 유의할 점: 기본 클래스의 생성자가 호출되는 도중에는, 객체의 타입이 기본 클래스가 된다는 것.
- 기본 클래스의 생성자가 호출되는 동안, 파생 클래스는 초기화된 상태가 아니다.
- 그런데 가상 함수를 파생 클래스쪽에서 호출하면 파생 클래스의 멤버를 건드릴 가능성이 크다.
- 따라서 이런 일을 미연에 방지하기 위해, 이 시점에는 객체의 타입을 아예 기본 클래스로 정해버린다.
- 따라서 호출되는 가상 함수는 모두 기본 클래스의 것으로 결정되며, 런타임 타입 정보(dynamic_cast, typeid 등)도 기본 클래스 타입의 객체로 결정한다.
- 소멸 과정도 똑같다.
- 소멸자는 파생 클래스 → 기본 클래스 순으로 호출된다.
- 파생 클래스의 소멸자가 호출되고 나면, 파생 클래스의 데이터 멤버들은 정의되지 않은 값들이 된다.
- 따라서 가상 함수가 제대로 동작하기 어려울 가능성이 높다.
- 또한, 기본 클래스의 소멸자에 진입하면 객체는 기본 클래스 객체가 된다.
그렇다면 기본 클래스 초기화 시점에 가상 함수의 기능이 필요할 때는?
- 기본 클래스 초기화 시점에 가상 함수의 기능이 필요하다는 것은, 생성 시점에 파생 클래스의 타입에 따라 다르게 동작해야할 함수가 필요하다는 것이다.
- 그러나 모든 파생 클래스에서 해당 작업을 수행하므로, 기본 클래스에다 통일하고 싶은 것이다.
- 대처 방법은 많다. 그중 책에서 언급한 하나를 소개한다.
- 파생 클래스에서 기본 클래스로 정보 넘겨주기
기존 예시
// 기본 클래스
class Transaction {
public:
Transaction();
virtual void logTransaction() const = 0; // 순수 가상함수
...
};
Transaction::Transaction() {
...
logTransaction(); // 거래 내역 로깅하는 함수
}
// 파생 클래스
class BuyTransaction : public Transaction {
public:
virtual void logTransaction() const;
...
};
// 파생 클래스
class SellTransaction : public Transaction {
public:
virtual void logTransaction() const;
...
};
- 모든 객체 생성 시점에 해당 객체의 타입에 따른 logTransaction()을 수행하고 싶다.
- 이 코드를 통해 BuyTransaction b; 를 선언하고 기본 클래스 생성자가 호출된다면 의도치 않은 동작이 발생한다.
- 기본 클래스 생성자에서 가상함수 호출 시, 기본 클래스의 함수가 호출되기 때문이다.
- 내가 원했던 건 타입에 따른 가상함수 호출이었음
- 물론 위 경우에 logTransaction()은 순수 가상함수이므로 링크 중에 에러 발생함
- 그러나 순수 가상함수가 아니라면, 그냥 기본 클래스의 것으로 호출될 것임.
- 기본 클래스 생성자에서 가상함수 호출 시, 기본 클래스의 함수가 호출되기 때문이다.
수정된 예시
class Transaction {
public:
explicit Transaction(const string& logInfo);
void logTransaction(const string& logInfo) const; // 비가상 함수
};
Transaction::Transaction(const string& logInfo) {
...
logTransaction(logInfo); // 생성자에서 비가상 함수 호출
};
class BuyTransaction : public Transaction {
public:
// 로그 정보를 기본 클래스 생성자로 넘김
BuyTransaction(parameters) : Transaction(createLogString(parameters)) { ... };
...
private:
static string createLogString(parameters);
};
- 기본 클래스 부분이 생성될 때는 가상 함수를 호출한다 해도 기본 클래스의 울타리를 넘어 내려갈 수 없다.
- 따라서, 기본 클래스 생성자로 필요한 초기화 정보를 받도록 해주고, 이 정보를 파생 클래스 쪽에서 기본 클래스 생성자로 올려주는 것이다.
- 이는 파생 클래스의 초기화 리스트에서 진행한다.
- 파생 클래스의 초기화 리스트에서 기본 클래스의 생성자를 호출하면, 해당 정보에 따라 기본 클래스의 생성자가 호출되고 초기화되기 때문이다.
생성자/소멸자에 바로 있는 가상함수 뿐만 아니라, 타고 타고 들어가서 호출되는 가상 함수도 있을 수 있다.
- 따라서 생성자/소멸자에서 호출하는 함수들이 가상 함수를 포함하고 있지는 않은지 잘 살펴보고 수정해야 한다.
'Effective > Effective C++' 카테고리의 다른 글
[EC++] 11. operator=에서는 자기대입에 대한 처리가 빠지지 않도록 하자 (0) | 2024.11.17 |
---|---|
[EC++] 10. 대입 연산자는 *this의 참조자를 반환하게 하자 (0) | 2024.11.17 |
[EC++] 8. 예외가 소멸자를 떠나지 못하도록 붙들어 놓자 (0) | 2024.11.16 |
[EC++] 7. 다형성을 가진 기본 클래스에서는 소멸자를 반드시 가상 소멸자로 선언하자 (0) | 2024.11.16 |
[EC++] 6. 컴파일러가 만들어낸 함수가 필요 없으면 확실히 이들의 사용을 금해 버리자 (3) | 2024.11.13 |
Comments