Item 9: 객체 생성 및 소멸 중에는 절대로 가상함수를 호출하지 말자

이유는 우리가 원하는 대로 돌아가지 않을 것이기 때문이다.

class Transaction {         // 모든 거래에 대한
public:                     // 기본클래스
    Transaction();
    virtual void logTransaction() const = 0; // 타입에 따라 달라지는
    ...                                      // 로그 기록을 만든다.
};

Transaction::Transaction()
{
    ...
    logTransaction();                        // 마지막 동작으로, 이 거래를
}                                            // 로깅하기 시작한다.
class BuyTransaction: public Transacton {
public:
    virtual void logTransaction() const;     // 이 타입에 따른 거래내역
    ...                                      // 로깅을 구현한다.
};

class SellTransaction: public Transaction {
public:
    virtual void logTransaction() const;     // 이 타입에 따른 거래내역
    ...                                      // 로깅을 구현한다.
};
BuyTransaction b;
1. 이 코드의 문제점은?
  • 파생 클래스 객체가 생성될 때 그 객체의 기본 클래스 부분이 파생 클래스 부분보다 먼저 호출된다.
  • 그런데 Transation의 생성자에서 logTransation이 호출되는데 이때의 logTransaction은 BuyTransaction의 것이 아니라 Transaction의 것이다.

기본클래스의 생성자가 호출될 동안에는, 가상 함수는 절대로 파생 클래스쪽으로 내려가지 않는다.

기본클래스가 생성/소멸되는 중 호출되는 가상함수는 모두 기본클래스의 것으로 결정(resolve)된다.

런타임 타입 정보를 사용하는 언어요소(dynamic__cast, type_id 등)를 사용해도 이 순간엔 모두 기본 클래스 타입으로 결정된다.


class Transaction {
public:
    Transaction() { init(); }
    virtual void Transaction() const = 0;
    ...
private:
    void init()
    {
        ...
        logTransaction(); // 비가상함수에서 가상함수 호출!!
    }
};
2. 이 코드의 문제점은?

컴파이도 잘되고 링크도 잘된다.

하지만 실행중 abort 될 것이다.

만약에 logTransaction이 순수가상함수가 아닌 일반 가상함수라면 어떤 logTransaction이 호출될지 머리가 아플 수 있다.

3. 이런 문제(기본클래스의 생성자에서 직접 가상함수를 호출하지 않고 일반 함수에서 가상함수를 호출하고, 이 일반함수를 생성자에서 호출할 때)의 해결 법은?

따로 없다. 열심히 코드를 보는 수밖에 없음.

  1. 생성자에서 가상함수를 호출하는 문제의 해결법은?

위 예제의 경우, Transaction::logTransaction 함수를 비가상함수로 만들고, 이 클래스의 생성자에 필요한 정보를 파생클래스에서 주도록 변경하면된다.

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: pulbic Transaction {
pubic:
    BuyTransaction(parameters)
    : Transaction(createLogString(parameters)) // 로그정보를 기본클래스
      { ... }                                  // 생성자로 넘긴다.
    ...
private:
    static createLogString(parameters);
};

정리

  • 생성자 혹은 소멸자 안에서 가상함수를 호출하지 말자. 가상함수라고 해도, 지금 실행중인 생성자나 소멸자에 해당되는 클래스는 파생 클래스 쪽으로 내려가지 않는다.

results matching ""

    No results matching ""