페이지

2015년 1월 4일 일요일

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

* 생성자 혹은 소멸자 안에서 강상 함수를 호출하지 마세요. 가상 함수라고 해도, 지금 실행중인 생성자나 소멸자에 대항되는 Class의 파생 Class 쪽으로 내려가지 않으니까요.

class Transaction {
public :
    Transaction () {
        ...
        logTransaction();
    }
    virtual void logTransaction() const = 0;
    ...
};

class BuyTrans : public Transaction {
public:
    virtual void logTransaction() const;
    ...
};

class SellTrans : public Transaction {
public:
    virtual void logTransaction() const;
    ...
};

BuyTrans b;

라는 Code를 실행할 때 어떻게 될까 ? 생성자 실행시 부모 Class의 생성자부터 먼저 실행된다. 부모 Class의 생성자 실행시 파생 Class 의 Data는 아직 생성전이므로 가상 함수는 절대로 파생 Class 쪽으로 내려가지 않고, 부모 Class 것이 실행된다. 부모 Class의 생성자가 실행될 동안은 그 Object의 Type 또한 부모 Class 이다.

소멸자가 호출될때는 어떨까 ? 똑같이 생각하면 된다. 파생 Class의 소멸자가 먼저 실행되고, 파생 Class의 Data가 정의되지 않은 상태로 된 후에 부모 Class의 소멸자가 호출 된다.

생성자가 많은 경우 공통되는 부분을 Init() 함수로 빼는 경우가 많다. 이 Init() 함수에서 가상 함수를 호출하면 어떻게 될까 ? 개념적으로는 위와 같지만 사악한 걸로 따지면 한 술도 모자라 몇 술 더 뜨는 코드이다. 왜냐면 위의 Code와는 달리 Link 에러도 없고, Compile도 잘되기 때문이다.

이 문제를 해결하는 방법은 logTransaction() 을 비가상 멤버 함수로 바꾸는 것이다. 그리고 나서 파생 Class의 생성자들은 로그 정보를 부모 Class의 생성자로 넘겨야 한다.

class Transaction {
public :
    explicit Transaction (const std::string& logInfo) {
        ...
        logTransaction(logInfo);
    }
    void logTransaction(const std::string& logInfo) const { ... }
    ...
};

class BuyTrans : public Transaction {
public:
    BuyTrans( parameters ) : Transaction(createLogString( parameters )) { ... }
    ...
private:
    static std::string createLogString( parameters ) { ... }
};

createLogString() 라는 정적 함수가 사용되었다. 이 함수는 부모 Class 생성자 쪽으로 넘길 값을 생성하는 용도로 쓰이는 도우미 함수인데, 기본 Class의 멤버 초기화 리스트가 만수산 드렁칡처럼 달려 있는 경우에 특히 편리하다. 정적 멤버로 되어 있기 때문에, 생성이 채 끝나지 않은  자식 Class 객체의 미초기화된 Data 멤버를 자칫 실수로 건드릴 위험도 없다.

댓글 없음:

댓글 쓰기