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 멤버를 자칫 실수로 건드릴 위험도 없다.
댓글 없음:
댓글 쓰기