728x90
반응형

먼저 이 단원에서 얘기하고 싶은 내용을 얘기하자면 객체 생성 및 소멸 과정에서 가상 함수를 호출하면 "미정의 동작"을 할 수 있기 때문에 호출하면 안 된다는 것이다.

이런 현상이 발생하는 원인에 관해서 알아보자.

 

다형성과 가상 함수

base클래스에서 가상 함수 function()을 선언하고, derived클래스에서 가상 함수 function을 오버라이딩 해준 구조를 생각해보자.

이제 메인에서 base b = new drived(); 이후 b.function(); 호출 시 derived의 함수가 호출되는데, 이게 바로 C++의 다형성의 특징이다.

 

근데  base의 생성자에서 가상 함수인 function을 호출하게 되면 어떻게 될까?

일단 예제를 확인해보자.

class Transaction
{
public:
	Transaction();
	virtual void logTransaction() const = 0; // 타입에 따라 달라지는 로그 기록    
}

Transaction::Transaction()
{
	logTransaction(); // 기본 클래스의 생성자에서 가상 함수 호출..!!
}

class BuyTransaction: public Transaction // Transaction의 파생클래스
{
public:
	virtual void logTrnasaction() const;
}

int main()
{
	BuyTansaction b;
	return 0;
}

메인을 보면 BuyTransaction의 생성자가 호출되는 것은 맞다. 그러나 이 전에 Transaction의 생성자가 먼저 호출됨을 알 수 있는데 여기서 logTransation은 Transaction의 함수이다.

그 이유는, 기본 클래스 생성자가 돌아가고 있을 때 파생 클래스는 초기화 이전 상태이기 때문에 기본 클래스의 함수를 호출하게 되며 '미정의 동작'이 일어나게 된다.

물론, 생성자뿐만 아닌 소멸자도 동일한 문제가 발생한다.

 

해결 방법

여러 가지 방법을 찾을 수 있는데, 그중 한 가지를 소개하자면 가상 함수가 아닌 비가상 멤버 함수를 사용하는 것이다.

위의 예제 같은 경우 타입에 따라 달라지는 로그 기록을 노출하고 싶으니, 파생 클래스의 생성자들로 하여금 필요한 로그 정보를 Transaction의 생성자로 넘기고 logTransaction을 비가상 함수로 변경하면 BuyTransaction객체의 초기화되지 않은 멤버를 건드릴 위험이 없기에 안전해진다.

class Transaction
{
public:
	explicit Transaction(const std::string& logInfo); // explicit 자신이 원하지 않은 형변환이 일어나지 않도록 제한
	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);
}

 

요약

  • 생성자 혹은 소멸자 안에서 가상 함수를 호출하면 안 된다. 가상 함수라고 해도, 지금 실행 중인 생성자나 소멸자에 해당되는 클래스의 파생 클래스 쪽으로 내려가지 않기 때문이다.
728x90
반응형