객체 생성 및 소멸 과정 중에는 가상함수를 호출하면 절대로 안 됩니다!!
우선 코드부터 봅시다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | #include<iostream> class Transaction { public: Transaction(); virtual void logTransaction() const = 0; }; Transaction :: Transaction() { /** * 생성자에서 순수가상함수를 호출했음 */ logTransaction(); } class BuyTransaction : public Transaction { public: virtual void logTransaction() const; }; void BuyTransaction::logTransaction() const { } class SellTransaction : public Transaction { public: virtual void logTransaction() const; }; void SellTransaction::logTransaction() const { } int main() { BuyTransaction b; return 0; } |
위의 코드는 다행스럽게도(?) LINK 에러를 내준다.
언뜻 봐서는 부모클래스의 순수가상함수를 자식들이 재정의 했고, BuyTransaction 선언으로
부모생성자가 호출되면서, 자식클래스의 정의함수로 넘어가게끔 되어 문제가 없어 보인다.
하지만,
이 코드가 LINK 에러를 내뱉는 이유는?
BuyTransaction 클래스는 Transaction 클래스를 상속받았기 때문에
상속받은 클래스의 객체가 생성될 때에는 부모 클래스의 생성자가 먼저 호출된다.
부모의 생성자가 먼저 호출된 상태에서 가상 함수를 호출하면, 현재 정의가 되지 않았기 때문에 오류가 생기게 된다.
즉!
부모의 생성자가 먼저 호출된다는 것은 그 동안에는 부모의 클래스 타입, 즉 Transaction 클
래스 타입으로 인식을 하게 된다는 것입니다.
그러므로, Transaction()함수의 정의는 찾을 수 없으니까 LINK에러가 나는 것입니다.
소멸자의 경우도 비슷하게 생각하면 된다.
파생 클래스에서 정의된 소멸자가 수행되고, 부모 클래스의 소멸자가 수행이된다. 여기서 부모 클래스 소멸자가 수행될 때는 그 객체가 부모 클래스 타입이라고 인식하게 된다는 것이다.
위의코드가 에러가 나지 않는다면, 끔찍한 일(?)이 발생 합니다.
가장중요하게 우리가 알아야 할 부분은?
1. 생성자 및 소멸자에서는 가상함수를 호출 하면 안된다.
2. 파생 클래스의 생성자 및 소멸자 수행시 부모 클래스의 생성자/소멸자를 수행할 때, 컴파일러는 부모 클래스의 타입으로 인식하게 된다
그러면, 위의코드처럼 우리가 원하는건 파생클래스 객체 선언과 동시에 로그정보를 호출되기를 원한다고 가정 해봅시다. 방법은 많이 있겠지만, 책의 나와 있는 방법으로 간단하게 해결하도록 하겠습니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | #include<iostream> class Transaction { public: explicit Transaction(const std::string& logInfo); void logTransaction(const std::string& logInfo) const; }; Transaction::Transaction(const std::string& logInfo) { logTransaction(logInfo); } void Transaction::logTransaction(const std::string& logInfo) const { std::cout<<logInfo.c_str()<<std::endl; } class BuyTransaction : public Transaction { public: BuyTransaction(const char * log) : Transaction(createLogString(log)){ } private: static std::string createLogString(const char * log) { return log; } }; int main() { BuyTransaction b("TEST"); return 0; } |
위의 코드는 순수가상함수였던 (logTransaction) 함수를 비가상함수로 바꾸고, 자식클래스 생성자들로 하여금 필요한 로그정보를 부모생성자로 넘기고 있습니다.
이것 만은 잊지 말자!
◆ 생성자 혹은 소멸자 안에서 가상 함수를 호출하지 마세요. 가상 함수라고 해도, 지금 실행 중인 생성자나 소멸자에 해당된느 클래스의 파생클래스 쪽으로는 내려가지 않으니까요
'0x0001 > Effective C++' 카테고리의 다른 글
[Effective C++] 항목 11 : operator=에서는 자기대입에 대한 처리가 빠지지 않도록 하자 (0) | 2019.02.12 |
---|---|
[Effective C++] 항목 10 : 대입 연산자는 *this의 참조자를 반환하게 하자 (0) | 2019.02.11 |
[Effective C++] 항목 8 : 예외가 소멸자를 떠나지 못하도록 붙들어 놓자 (0) | 2019.02.10 |
[Effective C++] 항목 7 : 다형성을 가진 기본 클래스에서는 소멸자를 반드시 가상 소멸자로 선언하자 (0) | 2019.02.10 |
[Effective C++] 항목 6 : 컴파일러가 만들어낸 함수가 필요 없으면 확실히 이들의 사용을 금해버리자 (0) | 2019.02.10 |