<예시>

 

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
class Transaction
{
public:
    Transaction();
 
    virtual void logTransaction() const = 0;
};
 
Transaction::Transaction()
{
    logTransaction();
}
 
class BuyTransaction : public Transaction
{
public:
    virtual void logTransaction() const;
};
 
class SellTransaction : public Transaction
{
public:
    virtual void logTransaction() const;
};
 
 
int _tmain(int argc, _TCHAR* argv[])
{
    BuyTransaction buy;
 
    return 0;
}
cs

 

Transaction 클래스는 추상 클래스이다.

BuyTransaction, SellTransaction 클래스는 Transaction 클래스로부터 상속을 받았다.

main 함수에서 BuyTransaction 타입의 변수를 선언했다.

그렇다면 무엇이 문제의 소지가 되는 것일까??

 

상속 관계에서 생성자는 가장 위의 부모 클래스에서부터 맨 마지막 자식 클래스까지 순차적으로 호출된다.

그러면 BuyTransaction 타입의 buy 변수가 생성될 때는 Transaction 클래스의 생성자가 가장 먼저 호출된다.

그런데 Transaction 클래스의 생성자에는 logTransaction()를 호출하는 문장이 있다. 문제는 여기서 발생한다.

 

기본 클래스의 생성자가 호출될 동안에는, 가상 함수는 절대로 파생 클래스 쪽으로 내려가지 않는다.

즉, 기본 클래스의 생성자에서 호출되는 logTransaction 함수는 BuyTransaction의 함수가 아니라 Transaction의 함수가 호출

되는 것이다.

 

이유는 기본 클래스의 생성자가 동작하는 시점에는 아직 파생 클래스의 데이터들은 초기화가 되어있지 않은 상태이기 때문이다.

만약 기본 클래스에서 호출한 가상 함수가 파생 클래스의 가상 함수를 호출하게 된다면 파생 클래스의 가상 함수에는 분명히

파생 클래스의 멤버 변수들을 사용하는 문장들이 있을텐데 아직 초기화되어 있지 않은 멤버들에 접근하는 것은 문제의 원인이 된다.

 

더 핵심적인 사실은 기본 클래스의 생성자가 동작하는 동안에는 선언한 변수 타입과 상관없이 기본 클래스 타입으로서 변경된다.

즉, BuyTransaction 타입으로 변수를 생성하였다 하더라도 기본 클래스 부분은 초기화하기 위해 Transaction 생성자가 실행되는 동안은

Transaction 타입으로 선언된 것 마냥 동작된다는 것이다.

파생 클래스의 데이터가 아직 초기화가 되지 않은 상태이므로 아예 없었던 것처럼 취급하는 편이 가장 안전하기 때문이다.

 

소멸자 또한 마찬가지이다.

파생 클래스의 소멸자가 호출되고 나면 파생 클래스의 데이터 멤버는 정의되지 않은 값으로 가정하기 때문에.

c++는 이들을 없는 것처럼 취급하고 진행한다. 기본 클래스 소멸자에 진입할 당시의 객체는 기본 클래스 객체가 된다.

 

해결 방법

 

1. logTransaction을 Transaction 클래스의 비가상 멤버 함수로 변경.

2. 파생 클래스의 생성자들로 하여금 필요한 로그 정보를 Transaction의 생성자로 넘기기.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Transaction
{
public:
    explicit Transaction(const std::string& logInfo);
 
    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.. )
};
 
 
cs

 

다시 정리하자면, 기본 클래스 부분이 생성될 때 가상 함수를 호출한다고 해도 기본 클래스 범위를 벗어나지 않는다.

그러므로 필요한 초기화 정보를 파생 클래스 쪽에서 기본 클래스 생성자로 올려주도록 만듦으로써 부족한 부분을

역으로 채워 나가는 것이다.

(생각해보면 처음 배울때부터 자식 클래스에서 부모 클래스로 정보를 넘기는 방식을 당연하듯이 배웠는데...

 부모 클래스에서 자식 클래스에 접근해서 초기화하는 방식을 배우지 않은 이유 자체가 이것 때문이였던 것임)

 

그리고 또 중요한 점!!

자식 클래스에서 부모 클래스로 초기화 정보를 넘겨줄 때 private인데 static인 createLogString 함수를 이용해서

초기화를 도운다.

static 함수인데 private으로 선언되어서 미친짓이라고 생각할 수 있지만... 정적 멤버 함수이기 때문에 초기화가

되지 않은 멤버 함수를 자칫 실수로라도 건드릴 위험 자체가 없어지기 때문에 안정성이 높아진다.

(정적 멤버 함수 내에는 일반 멤버 함수 사용 불가)

 

1줄 요약

 

 - 생성자 혹은 소멸자 안에서 가상 함수를 호출하지 말자. 가상 함수라고 해도, 지금 실행 중인 생성자나 소멸자에

   해당되는 클래스의 파생 클래스 쪽으로는 내려가지 않기 때문이다.

+ Recent posts