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


이번 항목은 시작하기 전에

몇 가지 중요한 부분을

짚고 넘어가도록 하겠다.


1. Base 클래스의 생성자가 호출될 동안에는,

가상 함수는 절대로 Drived 클래스로 내려가지 않는다.


2. Base 클래스 생성자가 돌아가고 있을 시점에

Drived 클래스 데이터 멤버는

초기화된 상태가 아니다.


3. Drived 클래스 객체의 Base 클래스 부분이

생성되는 동안은, 그 객체의 타입은 바로

Base 클래스이다.




사실 위의 내용이

생성자와 소멸자 내에서 왜 가상함수를

넣으면 안되는지에 대한 답변이 되겠다.

자 이제 예제를 보며 찬찬히 짚어보자.


생성될 때마다 로그 기록을 남겨야하는 클래스가 있다.

class Transaction {

public:

Transaction();

virtual void logTransaction() const = 0;

...

};

Transaction::Transaction(){

logTransaction();

}


class BuyTran : public Transaction{

public:

virtual void logTransaction const;

...

};


class SellTran : public Transaction {

public:

virtual void logTransaction() const;

...

};


int main(){

BuyTran b;

}


1. Base 클래스의 성성자가 먼저 호출된다.

2. Drived 클래스의 생성자가 호출된다.

3. Dirved 클래스의 소멸자가 호출된다.

4. Base 클래스의 소멸자가 호출된다.

위와 같은 순서는 잘 알고 있을 것이다.




BuyTran b;

Drived 클래스의 객체로 선언하고 있지만

실제로 호출되는 것은 Base 클래스의

logTransaction()이다.

왜?? 위에 3가지 언급했던 부분을 참고하기 바란다.




다시 정리하자.

Base 클래스가 호출될 동안에는 그 객체의 타입은

Base 클래스이며, 호출되는 가상 함수는 모두

Base 클래스의 것이다.

또한 이 시기에 Drived 클래스 생성자는 호출되지 않으며

자연스레 Drived 클래스의 멤버는 초기화가 되지 않는다.

따라서 가상 함수는 절대로 Drived 클래스로 내려가지 않는다.




다시 예제를 보자.

위의 경우, logTransaction이

순수 가상함수로 선언이 되어 있기 때문에

Base 클래스 생성자에서 호출이 되면

함수의 정의 부분이 없기 때문에 링커 에러가 난다.

(Base 클래스 생성자에서 호출되는 logTransaction은

순수 가상함수로 선언된 자기 자신의 멤버 함수이기 때문)

따라서 오류를 찾기 쉬운 경우이다.




다음 예제를 보자.

class Transaction {

public:

Transaction(){

Init();

}

virtual void logTransaction() const = 0;

...


private:

void Init(){

logTransaction();

}

};


이 코드는 개념적으로 볼 때 위의 코드와 일맥상통하다.

하지만 위의 코드와는 다르게 에러를 발생시키기 않는다. 왜??




위의 logTransaction() 이 순수 가상 함수가 아닌

일반 가상 함수라하면 문제는 더더욱 커진다.

정상적으로 Transaction 객체의 logTransaction()함수가

정상적으로 호출될 것이기 때문이다.

(순수 가상 함수가 아니기 때문에 링커 에러도 없다.)




--> 해결책

생성 중이거나 소물 중인 객체에 대해 생성자나 소멸자에서

가상 함수를 호출하는 코드를 철저히 솎아내자!


하지만 모든 객체가 생성될 때마다

log파일을 만들어야 하는데 이러한 부분의

구현도 만만치 않다. 해결책이 없을까?


--> 해결책

logTransaction을 Transaction 클래스의

비가상 멤버 함수로 바꾸는 것이다.

말로만 해서는 모르겠다. 예제를 보자.


class Transaction {

public:

Transaction(const string& logInfo);

void logTransaction(const string& logIngo) const;

};

Transaction::Transaction(const string& logInfo){

...

logTransaction(logInfo);

}


class BuyTran : public Transaction {

public:

BuyTran (string& _logName ) : Transaction(createLogString (_logName)) {

...

}

private:

static string createLogString (string& _logName);

};


필요한 초기화 정보를 Drived클래스 쪽에서

Base 클래스 쪽으로 올려주도록 만든다.




짚고 넘어가기.

createLogString() 함수는

drived 클래스에서 base클래스의 생성자 쪽으로

값을 넘기기 위한 도우미 함수이다.

static으로 선언하는 이유는

생성이 끝나지 않은 Drived클래스의 멤버를

건드릴 위험성을 배제하기 위함이다.

(static은 객체 생성 전에 이미 생성되어져 있기 때문)




POINT!!

생성자 및 소멸자 안에서 가상 함수를 호출하지 말자!!






+ Recent posts