2016년 3월 18일 금요일

[Effective C++] 항목 9 : 객체 생성 및 소멸 과정중에는 절대 가상 함수를 호출하지 말자!!!

일단은 객체의 생성 과정중에 가상함수를 호출 하면 어떤일이 발생하는지 알아 보겠는데, 그전에 객체의 생성과정중 객체 생성 순서를 알아 보도록 하겠습니다. 

  1. #include <iostream>  
  2. using namespace std;  
  3.   
  4. class Base  
  5. {  
  6. public:  
  7.     Base(){cout<<"base class 생성자 호출"<<endl;}  
  8.     ~Base(){cout<<"base class 소멸자 호출"<<endl;}  
  9. };  
  10.   
  11. class Derived :public Base  
  12. {  
  13. public:  
  14.     Derived(){cout<<"Derived class 생성자 호출"<<endl;}  
  15.     ~Derived(){cout<<"Derive class 소멸자 호출"<<endl;}  
  16. };  
  17.   
  18. int main()  
  19. {  
  20.     Derived* d = new Derived;  
  21.       
  22.       
  23.     delete d;  
  24.   
  25.     return 0;  
  26. }  

 일반적으로 파생 클래스에서 객체가 생성될때 기본 클래스에서 생성자가 먼저 호출이 됩니다.  그 다음 파생 클래스 생성자 호출 소멸자 호출, 그리고 마지막으로 기본 클래스의 소멸자가 호출되는 순서를 가집니다.  하지만 여기서 기본 클래스에서 가상함수 Call을 호출하고 있다고 가정해 봅시다. 
  1. #include <iostream>  
  2. using namespace std;  
  3.   
  4. class Base  
  5. {  
  6. public:   
  7.     Base(){cout<<"Base 생성자 호출"<<endl; Call();}  
  8.     virtual ~Base(){cout<<"Base 소멸자 호출"<<endl;}  
  9.     virtual void Call()=0;  
  10. };  
  11.   
  12. class Derived : public Base  
  13. {  
  14. private:  
  15.     const char* Message;  
  16. public:  
  17.     Derived(const char* _Message):Message(_Message) {cout<<"Derived 생성자 호출"<<endl;}  
  18.     ~Derived(){cout<<"Derived 소멸자 호출"<<endl;}  
  19.     void Call(){cout<<"Derived Call 함수 메세지 출력 : "<< Message<<endl;}  
  20.   
  21. };  
  22.   
  23. int main()  
  24. {  
  25.     Derived* d = new Derived("test");  
  26.       
  27.     delete d;  
  28.   
  29.     return 0;  
  30. }  
  일단 기본 클래스에서 함수를 호출을 했으므로 오버라이딩 하고 있는 Derived 클래스의 Call()함수를 호출하게 됩니다. 하지만 여기서 문제가 있습니다. 바로 호출되는건 Derived 클래스의 Call함수가 아닌 Base 클래스의 Call함수가 호출된다는것입니다. 그래서 아래와 같은 에러가 나옵니다. 
  앞서 Base 와 Derived 클래스의 생성과정을 얘기 했다시피, Bese 클래스는 Derived 클래스 생성자보다 앞서서 실행되기 때문에 Base 클래스 생성자가 돌아가고 있을 시점에 Derived 클래스 데이터 멤버는 아직 초기화된 상태가 아니기 때문입니다. 이런 초기화 되지 않는 영역을 건드린다는 것은 아주 위험성이 높은 상황이라고 말할 수 있습니다. 

  그럼 이런 문제에 대한 대처 방법에 대해서 알아 봅시다. 기본 클래스 부분이 생성될때는 가상 함수를 호출한다 해도 기본 클래스의 울타리를 넘어 내려갈 수 없기 때문에 가상 함수를 비가상 함수로 바꾸어  필요한 초기화 정보를 파생클래스 쪽에서 기본 클래스 생성자로 올려주도록 만들어 줌으로써 부족한 부분을 역으로 채운면 되는 것입니다. 무리 없이 출력 되는 것을 볼 수 있습니다.
  1. #include <iostream>  
  2. using namespace std;  
  3.   
  4. class Base  
  5. {  
  6. public:   
  7.     Base(const char* _Message){cout<<"Base 생성자 호출"<<endl; Call(_Message);}  
  8.     virtual ~Base(){cout<<"Base 소멸자 호출"<<endl;}  
  9.     void Call(const char* Message){cout<<Message<<endl;}  
  10. };  
  11.   
  12. class Derived : public Base  
  13. {  
  14. public:  
  15.     Derived(const char* _Message) : Base(_Message) {cout<<"Derived 생성자 호출"<<endl;}  
  16.     ~Derived(){cout<<"Derived 소멸자 호출"<<endl;}  
  17.   
  18. };  
  19.   
  20. int main()  
  21. {  
  22.     Derived* d = new Derived("test");  
  23.   
  24.     delete d;  
  25.     return 0;  
  26. }  

댓글 없음:

댓글 쓰기

[Effective C++] 항목 30 : 인라인 함수는 미주알고주알 따져서 이해해 두자.

인라인 함수를 사용하면 컴파일러가 함수 본문에 대해 문맥별 최적화를 걸기가 용이해집니다. 인라인 함수의 아이디어는  함수 호출문을 그 함수의 본문으로 바꿔치기하자는 것  남발했다가는 코드의 크기가 커질 게 뻔하다. 인라인 함수로 부풀려진 ...