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;
...
};
BuyTransaction b;
해당 코드를 보면 BuyTransaction의 생성자에서 logTransaction()은 BuyTransaction의 함수일 것 같지만, 실제로는 Transaction에서 호출된다. 즉, 기본 클래스의 생성자가 호출되는 동안에는 가상 함수가 절대로 파생 클래스 쪽으로 내려가지 않는다. 그 대신 객체 자신이 기본 클래스 타입인 것처럼 동작한다.
지금 해드린 이야기보다 사실 다섯 배는 더 핵심적인 이야기를 꺼낼 때가 왔습니다. 파생 클래스 객체의 기본 클래스 부분이 생성되는 동안은, 그 객체의 타입은 바로 기본 클래스 입니다. 호출되는 가상 함수는 모두 기본 클래스의 것으로 결정(resolve)될 뿐만 아니라, 런타임 타입 정보를 사용하는 언어 요소[이를테면 dynamic_cast(항목 27 참조)라든지 typeid같은것]를 사용한다고 해도 이 순간엔 모두 기본 클래스 타입의 객체로 취급합니다.
즉, BuyTransaction 객체의 기본 클래스 부분을 초기화하기 위해 Transaction 생성자가 실행되는 동안에는 그 객체의 타입이 Transaction이라는 것이다.
그러나 애초에 해당 함수가 순수 가상 함수로 선언되어 있으므로, 함수를 정의하지 않으면 링크 단계에서 오류가 발생한다. 결과적으로 그런 코드가 오히려 낫다고 볼 수도 있다.
class Transaction {
public:
Transaction()
{ init(); }
virtual void logTransaction() const = 0;
...
private:
void init()
{
...
logTransaction();
}
};
해당 코드가 의미하는 것은, 일반 가상 함수나 멤버 버전이 구현되어 있을 경우 컴파일도 링크도 정상적으로 이루어지기 때문에 오류를 감지할 방법이 없다는 것이다.
이러한 문제를 해결하기 위한 방법은 여러 가지가 있지만, 한 가지를 예로 들면 logTransaction을 Transaction 클래스의 비가상 멤버 함수로 변경하는 것이다.
그리고 파생 클래스의 생성자들이 필요한 로그 정보를 Transaction의 생성자로 전달하도록 규칙을 정한다면, logTransaction이 비가상 함수이기 때문에 Transaction 생성자는 이 함수를 안전하게 호출할 수 있다.
class Transaction{
public:
explicit Transaction(const std::string& logInfo);
void logTransaction(const std::string& logInfo) const;
...
};
Transaction::Transaction(const std::string& logInfo)
{
...
logTransaction(logInfo);
}
class BuyTransaction: public Transaction{
public:
BuyTransaction(parameters): Transaction(createLogString(parameters)){ ... }
...
private:
static std::string createLogString(parameters);
};
위 코드를 설명하자면, 기본 클래스 부분이 생성되는 동안에는 가상 함수를 호출하더라도 파생 클래스 쪽으로 내려갈 수 없다.
따라서 필요한 초기화 정보를 파생 클래스에서 기본 클래스의 생성자로 ‘올려’ 주도록 설계한 것이다.
'C++ > Effective C++' 카테고리의 다른 글
| [C++] 11. operator=에서는 자기대입에 대한 처리가 빠지지 않도록 하자 (0) | 2025.11.19 |
|---|---|
| [C++] 10. 대입 연산자는 *this의 참조자를 반환하게 하자 (0) | 2025.11.19 |
| [C++] 8. 예외가 소멸자를 떠나지 못하도록 붙들어 놓자 (0) | 2025.11.17 |
| [C++] 7. 다형성을 가진 기본 클래스에서는 소멸자를 반드시 가상 소멸자로 선언하자 (0) | 2025.11.16 |
| [C++] 6. 컴파일러가 만들어낸 함수가 필요 없으면 확실히 이들의 사용을 금해버리자 (0) | 2025.11.15 |