본문 바로가기

Effective C++ 정리

09. 생성자, 소멸자에서 가상 함수를 호출하지 않는다.

기본 클래스 생성자에서 호출한 가상 함수는 파생 클래스쪽으로 내려가지 않는다

// 모든 거래에 대한 기본 클래스
class Transaction
{
public:
	Transaction();

	// 클래스 타입에 따라 달라지는 거래내역을 로깅하는 함수
	virtual void LogTransaction()	const = 0;

	...
};

Transaction::Transaction()
{
	...

	// 기본 클래스 생성자의 마지막 동작에서
	// 거래내역을 로깅하기 시작한다.
	LogTransaction();
}

// Transaction의 파생 클래스
class BuyTransaction :
	public	Transaction
{
public:
	// 이 클래스 타입(구매 기록)에 따른 거래내역을 로깅한다.
	virtual void LogTransaction()	const;
	...
};

// Transaction의 파생 클래스
class SellTransaction	:
	public	Transaction
{
	// 이 클래스 타입(판매 기록)에 따른 거래내역을 로깅한다.
	virtual void LogTransaction()	const;
	...
};
{
	// main 함수
	// BuyTransaction 자식 객체 생성
	CBuyTransaction	buyTr;
}

 

메인 함수에서 구매내역을 처리하는 buyTr 자식 객체를 생성했습니다.

그렇다면 기본 클래스 생성자인 Transaction 생성자가 먼저 호출 될 것입니다.

그런데 기본 클래스 생성자를 살펴보니 LogTransaction이라는 가상 함수가 보입니다.

 

이 때, 기본 클래스 생성자에서 호출 되는 가상 함수는

파생 클래스 쪽으로 내려갈 수 없습니다.

 

파생 클래스의 생성자는 아직 호출 되지 않았습니다.

그렇다면 파생 클래스의 데이터는 생성되지 않은 상태입니다.

이러한 타이밍에 동적 바인딩은 인식할 수 없습니다.

그래서 초기화되지 않은 파생 클래스의 데이터 영역을 건드리려고하면

프로그램의 미정의 동작이 흐를 수 있게 됩니다.

 

위에서 기본 클래스인 Transaction 생성자가 실행중일 때,

buyTr 자식 객체는 기본 클래스 객체로 취급됩니다.

파생 클래스의 생성자가 호출되고 실행되어야만 buyTr 객체는

자식 객체로서의 면모를 갖게 됩니다.

 

소멸자에서도 마찬가지로,

기본 클래스 소멸자에서 호출 되는 가상 함수는

파생 클래스 쪽으로 내려갈 수 없습니다.

 

기본 클래스의 소멸자가 실행중이면

이미 파생 클래스의 소멸자는 완료가 된 상태입니다.

 

파생 클래스의 데이터 멤버는 존재하지 않는 값으로 취급되서

이후에 기본 클래스 소멸자에서 호출되는 모든 가상 함수는 

기본 클래스 객체의 자격으로 처리 됩니다.

파생 클래스 쪽으로 내려갈 수 없습니다.

 

기본 클래스 생성자에서 호출할 함수를 비가상 멤버 함수로 바꿔보겠습니다.

그리고 파생 클래스 생성자에서 처리하는 정보를 기본 클래스 생성자로 넘기겠습니다.

 

class Transaction
{
public:
	explicit Transaction(const std::string strLogInfo);

	// 비가상 함수로 바꾼다.
	void LogTransaction(const std::string strLogInfo)	const;

	...
};

Transaction::Transaction(const std::string strLogInfo)
{
	...

	// 기본 클래스의 생성자에서 비가상 함수롤 호출한다.
	// 파생 클래스로부터 필요한 정보를 넘겨 받아서 채울 수 있게 된다.
	LogTransaction(strLogInfo);
}

class BuyTransaction :
	public Transaction
{
public:
	// 파생 클래스 생성시 필요한 로그 정보를 기본 클래스 생성자로 넘긴다.
	BuyTransaction(const string& strLogString)	:
		Transaction(CreateLogString(strLogString))
	{
		...
	}

private:
	static std::string CreateLogString(const string& strLogString);
};

 

파생 클래스에서 정의된 CreateLogString 함수는 정적 멤버로 구성되어 있습니다.

이 기능은 멤버 초기화 리스트에서도 편하게 사용할 수 있습니다.

 

이를 통해 기본 클래스에서 가상 함수를 호출하지 않고

파생 클래스 생성자에서 처리하게 된 정보를 기본 클래스로 넘길 수 있게 되었습니다.

 

또한, 정적 멤버로 되어 있기 때문에 객체 생성이 끝나지 않은

BuyTransaction 객체의 데이터 멤버를 건드릴 위험을 줄일 수 있게 되었네요.

정적 멤버는 this 포인터를 가지지 않기 때문입니다.