레이블이 virtual인 게시물을 표시합니다. 모든 게시물 표시
레이블이 virtual인 게시물을 표시합니다. 모든 게시물 표시

2016년 3월 13일 일요일

[c++] 가상함수, 가상함수테이블

동작원리:  가상함수는 어떻게 동작을 할까? 객체가 생성되면 멤버 함수는 메모리의 코드영역에 올라가게 된다. 우리가 생성한 객체는 코드 영역에 있는 이 멤버함수를 공유하게 것이죠. 지만 클래스에 한개 이상의 가상 함수가 있을 경우에는 컴파일러는 실제 호출되어야 할 함수의 위치 정보를 가지고 있는 가상 함수 테이블(Vritual Table) 이라는 것을 만들고, 클래스의 객체에는 가상 함수 테이블을 위한 포인터가 멤버 변수로 추가 시킵니다. 일반적으로 가상 함수가 있는 객체는 가상 함수 테이블에 있는 함수만을 호출하는 원칙을 가지고 있다.


단점? : 가상 함수를 사용해서 생기는 단점은 두가지를 들 수 있다. 첫번째로, 가상함수를 쓰면 가상함수 테이블을 만드므로 그만큼의 메모리 공간의 소모가 일어 나게 되기 마련이고, 두번째로 직접 가야 할 부분을 이 가상함수 테이블을 거쳐 가야 하니까 속도면에서 약간의 차이가 나게 되는 단점이 있습니다. 이렇게 단점이 있는것을 왜 쓰냐? 그만큼의 장점 부분이 차지하는 부분이 더 크기 때문입니다.

[c++] virtual 소멸자

곧바로 예제부터 보자.
  1. #include <iostream>  
  2. using namespace std;  
  3.   
  4. class AAA  
  5. {  
  6.     char* str1;  
  7. public:  
  8.     AAA(char* _str1){  
  9.         str1= new char[strlen(_str1)+1];  
  10.         strcpy(str1, _str1);  
  11.     }  
  12.     ~AAA(){         // virtual ~AAA()  
  13.         cout<<"~AAA() call!"<<endl;  
  14.         delete []str1;  
  15.     }  
  16.     virtual void ShowString(){  
  17.         cout<<str1<<' ';  
  18.     }  
  19. };  
  20.   
  21. class BBB : public AAA  
  22. {  
  23.     char* str2;  
  24. public:  
  25.     BBB(char* _str1, char* _str2) : AAA(_str1){  
  26.         str2= new char[strlen(_str2)+1];  
  27.         strcpy(str2, _str2);  
  28.     }  
  29.     ~BBB(){  
  30.         cout<<"~BBB() call!"<<endl;  
  31.         delete []str2;  
  32.     }  
  33.     virtual void ShowString(){  
  34.         AAA::ShowString();  
  35.         cout<<str2<<endl;  
  36.     }  
  37. };  
  38.   
  39. int main()  
  40. {  
  41.     AAA * a=new BBB("Good""evening");  
  42.     BBB * b=new BBB("Good""morning");  
  43.   
  44.     a->ShowString();   
  45.     b->ShowString();  
  46.   
  47.     cout<<"-----객체 소멸 직전----"<<endl;  
  48.     delete a;  
  49.     delete b;  
  50.   
  51.     return 0;  
  52. }  
 위 코드를 보면, AAA클래스에서 생성자에서 동적 할당 하기에 소멸자에서 메모리 해제 하고 있고 마찬가지로 BBB클래스의 생성자에서 동적 할당을 하고 있어서 소멸자에서 메모리 해제 하고 있는 형태를 가지고 있다. BBB클래스의 객체가 소멸될때, AAA클래스의 소멸자도 호출이 된다. 
  BBB클래스 객체가 생성이 되면, AAA클래스의 생성자에 의해서도 메모리 공간 동적 할당 할것이고, BBB클래스도 마찬가지이다. 이 두곳에서 할당된 메모리 공간이 적절히 해제 될것이기에 우리가 신경 쓰지 않아도 되지만, 여전히 문제가 존재 한다. 
  1. int main()  
  2. {  
  3.     //AAA * a=new BBB("Good", "evening");  
  4.     BBB * b=new BBB("Good""morning");  
  5.   
  6.     a->ShowString();   
  7.     b->ShowString();  
  8.   
  9.     cout<<"-----객체 소멸 직전----"<<endl;  
  10.     //delete a;  
  11.     delete b;  
  12.   
  13.     return 0;  
  14. }  
 위의 코드는 정상적인 코드 이다. BBB클래스의 객체가 생성되는 과정에서 AAA클래스의 생성자도 호출되므로, 소멸될때는 AAA,BBB클래스의 소멸자가 아래와 같이 다 호출이 되는것을 알 수 있다.

그럼 다음 코드를 보자.
  1. int main()  
  2. {  
  3.     AAA * b=new BBB("Good""morning");  
  4.     b->ShowString();  
  5.   
  6.     cout<<"-----객체 소멸 직전----"<<endl;  
  7.   
  8.     delete b;  
  9.   
  10.     return 0;  
  11. }  
 위의 경우를 생각해보자. BBB클래스는 AAA클래스를 상속하기 때문에 선언부가 3번째 줄처럼 바뀔수도 있을 것이다. 하지만 실행해보면, AAA클래스의 소멸자만 호출 되고 있음을 알 수 있다. 바로 이 부분에서 메모리의 유출이 발생된다. 

 AAA클래스의 포인터로 참조 하지만, 생성되는 객체는 B클래스의 객체이기 때문에, AAA,BBB 클래스에서도 생성자부분에서 메모리 공간이 동적 할당되는데는 문제가 없다. 하지만 객체가 소멸될때 A클래스 내에서 동적 할당한 메모리 공간은 반환되지만, BBB는 반환되지 않았던 것이다. 객체가 소멸하고자 했을때 소멸의 주체가 되는 포인터가 AAA클래스의 포인터였기 때문에 이런 일이 발생한다. 

그럼 위 문제를 어떻게 해결 할 것인가? 바로 Virtual 소멸자(virtual destructor)을 써줌으로써 간단하게 해결이 된다. 아래와 같이 간단하게 virtual 키워드만 소멸자 앞에 붙여주면 되는 것이다. 
  1. class AAA  
  2. {  
  3.       
  4. public:  
  5.       
  6.     virtual ~AAA(){         // virtual만 붙여주면 된다.  
  7.         cout<<"~AAA() call!"<<endl;  
  8.         delete []str1;  
  9.       
  10. };  
 virtual 소멸자의 경우 AAA클래스를 상속하고 있는 BBB클래스의 소멸자를 호출하게 된다. 이어서 BBB클래스가 AAA클래스를 상속하고 있으므로 AAA클래스의 소멸자를 호출하게 됨으로써 소멸자들이 정상적으로 호출되는 결과를 볼수 있다. 

2016년 3월 12일 토요일

[c++] 오버라이딩, virtual, 은닉효과

오버라이딩(Overriding)이란 기본 클래스에 선언된 멤버와 같은 형태의 멤버를 파생 클래스에서 선언하는 것이다. 오버라이딩에 관해서 더 자세히 알아 보기 전에 오버라이딩을 재정의로 알고 있으신분들은 오버라이딩에 등장하는 특성이 재정의 인거지 오버라이딩 자체는 재정의가 아닌것을 명심하고 들어가보자.

은닉 효과 
  1. #include <iostream>  
  2. using namespace std;  
  3.   
  4. class AAA  
  5. {  
  6. public:  
  7.     void fct(){  
  8.         cout<<"AAA"<<endl;  
  9.     }  
  10. };  
  11.   
  12. class BBB : public AAA  
  13. {  
  14. public:   
  15.     void fct(){ //AAA 클래스의 fct() 함수를 오버라이딩.  
  16.         cout<<"BBB"<<endl;  
  17.     }  
  18. };  
  19.   
  20. int main(void)  
  21. {  
  22.     BBB b;  
  23.     b.fct();  
  24.   
  25.     return 0;  
  26. }  

  소스코드와 같은 상황에서 "AAA 클래스의 fct 함수는 BBB 클래스에 의해서 오버라이딩 되었다" 라고 한다. B객체를 생성하면 그림과 같은 형태를 가지는데 오버라이딩 되면 A클래스의 fct 함수는 B 클래스의 fct 함수에 의해서 가려지게된다. (이것은 은닉됐다고 한다.)
23번째 줄의 b.fct();는  B클래스의 fct 함수를 호출하게 된다. 따라서, 오버라이딩이라는 특성이 은닉이라는 특성을 보여준다. 그럼 이 은닉된 A 클래스의 fct 함수는 사용할 수 없는 것인가? 그렇지 않다



바로 이 은닉된것을 보게 하는것이 바로 포인터이다. 포인터를 이용하면 아래 그림과 같이 B객체를 B클래스의 포인터로 가리키면은 B클래스로 바라고, B클래스의 fct 함수를 호출이 가능한것이고, A클래스의 포인터로 B클래스를 가르키면, 그것은 A클래스를 바라보는 것이 된다. 그렇기 때문에 클래스 포인터의 접근권한은 A클래스에 종속이 되는 것이다. 
 그러면 직접 그렇게 되는지 소스 코드를 이용해 알아 보자. 출력 결과를 보면 그렇게 된다는 것을 알 수 있을 것이다. 
  1. #include <iostream>  
  2. using namespace std;  
  3.   
  4. class AAA  
  5. {  
  6. public:  
  7.     void fct(){  
  8.         cout<<"AAA"<<endl;  
  9.     }  
  10. };  
  11.   
  12. class BBB : public AAA  
  13. {  
  14. public:  
  15.     void fct(){  
  16.         cout<<"BBB"<<endl;  
  17.     }  
  18. };  
  19.   
  20. int main(void)  
  21. {  
  22.     BBB* b=new BBB;  
  23.     b->fct();  
  24.   
  25.     AAA* a=b;  
  26.     a->fct();  
  27.   
  28.     delete b;  
  29.     return 0;  
  30. }  

virtual : 그럼 멤버 함수를 가상으로 선언하면 어떻게 될까 가상이란 "실재 존재하지 않는것을 존재하는 것처럼 느끼게 하는것" 이다 fct 함수를 virtual로 선언하겠다. 이것은 원래는 이 함수는 없는 건데 내가 있는것 처럼 하겠다. 쉽게 말해 다시 말해 AAA 클래스 fct함수는 우리 눈에는 보이지만, 없는 것으로 해라는 말이다. 그럼 virtual 키워드를 쓰면 어떤 차이가 있는지 알아보자. 
  1. #include <iostream>  
  2. using namespace std;  
  3.   
  4. class AAA  
  5. {  
  6. public:  
  7.     virtual void fct(){  
  8.         cout<<"AAA"<<endl;  
  9.     }  
  10. };  
  11.   
  12. class BBB : public AAA  
  13. {  
  14. public:  
  15.     void fct(){  
  16.         cout<<"BBB"<<endl;  
  17.     }  
  18. };  
  19.   
  20. int main(void)  
  21. {  
  22.     BBB* b=new BBB;  
  23.     b->fct();  
  24.   
  25.     AAA* a=b;  
  26.     a->fct();  
  27.   
  28.     delete b;  
  29.     return 0;  
  30. }  


 메인에서 B클래스의 포인터로 A클래스의 포인터로 가르키고 있는데 a->fct(); 부분에서 A클래스의 포인터를 가르키면 A 클래스로 그 함수 부분을 호출 해야 할텐데 가리키는 곳을 가 봤더니 virtual 로 선언되어 있다. virtual은 없는것으로 치라는 것이다. 없으니까 어떤 것이라도출은 해야 하니 fct를 오버라이딩 하고 있는 BBB 클래스의 FCT 함수가 대신 호출이 일어 난다. 이런 특성이 바로 virtual을 이용한 재정의 이다. 그럼 우리는 다음과 같은 결론을 낼 수 있다. 
오버라이딩은 virtual 키워드는 넣어 주지 않으면 은닉의 특성은 넣어 주면 재정의 특성을 가진다

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

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