Total Articles 134
가상 함수의 활용 - Virtual Function ch.2
<가상 함수의 함정 - 생성자와 소멸자>
http://wahnfried.net
자식이 부모가 원하는 길을 가도록 하다가도 자식의 개성을 살려주는 일이란 퍽 어려운 일이라고 막상 적어놓고 보니, 이렇게 마음에 와 닿는 말도 없더군요. 헌데 마음만 그러했지 막상 두 번째 글을 쓰려고 보니 귀찮음에 손이 잘 가지 않아서 아주 오랜만에 업데이트를 합니다. 이 부분이 처음 프로그래밍을 할 적에 - 제대로 봐 두지 않았다면 - 헷갈리기 좋은 부분이었던 터라 퍽 애착이 가는 내용입니다. 이번에도 개성 있게 자식 키우기의 어려움에 대해서 알아볼까 합니다. 물론 C++ 에서의 이야기입니다;
- 생성자와 소멸자
그냥 간단히 만든 아래의 예제에서는 정말 간단히 부모 클래스인 CAnimal과 CDog을 한 번씩 생성했다가 지워봅니다.
예상하시듯이 위 코드에서 중요한 부분은 68번째 줄과 69번째 줄 두 줄 뿐입니다. 자식 클래스인 CDog을 생성할 때 부모 클래스와 자식 클래스의 생성자와 소멸자를 각각 한 번씩 호출하게 됩니다. 아래의 순서로 말입니다.
만약 부모 클래스인 CAnimal 을 생성할 때, CAnimal 클래스의 멤버 변수들에 대해 어떠한 처리를 해 주어야 한다거나, 마찬가지로 CAnimal 이 없어질 때 멤버 변수들의 메모리를 해제해주는 등의 처리가 필요하다면 위의 코드에서는 모든 기능들이 적절히 수행됩니다. 적어도 아직은요. 위의 코드에 약간의 장난을 쳐 보겠습니다.
그저 CDog 을 받는 포인터를 부모 클래스의 포인터로 바꾸어 보았습니다. 지난 챕터에서 그러했듯이 말입니다. 당연한 것이지만 생성자나 소멸자도 함수라고 생각한다면 동적 할당에서 벌어지는 문제는 이곳에서도 예외 없이 발생합니다. 무슨 말이냐면
라는 험한 상황이 벌어진다는 이야기입니다. 만약 CDog의 소멸자에서 풀어줘야 했던 메모리가 있다거나, 해야 할 다른 처리가 있었다면 위의 경우는 분명히 Memory leak 이나 여러 알 수 없는 문제를 일으키게 됩니다. 습관처럼 생성자와 소멸자에 여러 기능들을 넣어서 만들곤 하는 프로그래머라면 더더욱 신경 써야 하는 부분이겠지요. 지난 편을 꼼꼼하게 읽고 생각해 본 분이라면 예상했을 이야기이지만 복습 겸 해서 다시 한 번 강조해보기로 합니다 Effective C++ 에서도 초판부터 강조했던 이야기입니다. 놓칠 수 없겠죠. :-)
- 생성 & 소멸시의 또다른 함정
역시 다른 말 필요 없이 다음 코드로 가 보겠습니다. 요점은, Base Class 의 생성자에서 Say 를 한 번 하고 생성하고, 소멸자에서 Finalize 함수를 불러서 정리 작업을 마치고 클래스를 지우고 싶다.. 라는 것입니다.
생성자와 소멸자에서 불린 Say 와 Finalize 는 둘 다 virtual 로 선언되어 있으니까, 아마도 이 코드의 작성자 - 저겠죠; - 는 아래와 같은 의도로 코드를 작성했을 테죠. 하지만 예상과 실제는 다릅니다.
어째서인지는 앞서 알아보았던 부모 클래스와 자식 클래스의 생성자와 소멸자가 불리던 순서를 떠올려보면 알 수 있습니다. 생성-소멸의 프로세스와 함께 위의 flow를 따라가 보겠습니다.
마찬가지로 소멸자가 불렸을 때의 상황도 생각해 보면 다음과 같습니다.
이런 상황 때문에 생성자 - 소멸자 내에서 virtual 함수를 사용한다는 것은 일단 눈으로 보기에도 명쾌하지 않은 코드를 만드는 일이기도 하고 - 본인은 위 사항을 정확히 알고 만든 코드라고 해도 누군가는 왜 CDog의 Say 나 Finalize 가 불리지 않느냐에 대해 고민하게 될 지도 모르니까요 - 이것은 버그로 이어질 수도 있습니다. 때문에 아래의 사항을 기억해야 합니다.
- 조금 더 생각해보기
그렇다면 다음과 같은 것은 어떨까요? 아예 부모 클래스인 CAnimal 에는 Say() 라는 함수를 순수 가상함수로 선언해 놓고 똑같이 사용해 보는 것입니다.
결과는? 링크 에러입니다. Base Class 의 생성자/소멸자에서는 자신이 자식 클래스인지 부모인지에 관계 없이 무조건(!) Base Class 기준으로 함수를 호출한다는 걸 예상해볼 수 있는 테스트입니다.
조금만 더 고집을 부려볼까요? 생성자와 소멸자에서 위와 같은 문제가 벌어진다면, CAnimal의 생성자에서 보통의 함수인 Init() 함수를 호출하고, Init() 함수 안에서 순수 가상 함수인 Say() 를 호출하는 겁니다. 아래와 같이요.
거의 최후의 발악(?) 수준의 노력이고, 결과 역시 최후의 한 방 수준의 것이 등장합니다. 멋지게 빌드되어버리지만, 결과는 Runtime Error !! 말 그대로 돌려 봐야 아는 멋진 버그를 - 게다가 어플리케이션이 죽어 버리는 - 만들어 낸 셈입니다. 물론 이것을 우회할 수 있는 방법이 있기는 합니다만, 앞서 말씀드린 오늘의 요점을 반드시 기억해보도록 합니다.
- Outro
겪다 보면 예전에는 마냥 막연하다고만 생각했던 것들을 '아차!' 하며 뇌리에 떠올리게 될 때가 있습니다. 이 부분은 몇 년 전 제게 너무나 쿵(!) 했던 내용이었답니다 시간이 난다면 가상 함수를 썼을 때와 그렇지 않을 때의 메모리상의 주소 차이나, 함수 콜 시간의 차이 등도 한 번 정리해서 적어보았으면 좋겠군요. 아, 타입 캐스팅도 좋은 주제가 되겠네요.
<가상 함수의 함정 - 생성자와 소멸자>
http://wahnfried.net
자식이 부모가 원하는 길을 가도록 하다가도 자식의 개성을 살려주는 일이란 퍽 어려운 일이라고 막상 적어놓고 보니, 이렇게 마음에 와 닿는 말도 없더군요. 헌데 마음만 그러했지 막상 두 번째 글을 쓰려고 보니 귀찮음에 손이 잘 가지 않아서 아주 오랜만에 업데이트를 합니다. 이 부분이 처음 프로그래밍을 할 적에 - 제대로 봐 두지 않았다면 - 헷갈리기 좋은 부분이었던 터라 퍽 애착이 가는 내용입니다. 이번에도 개성 있게 자식 키우기의 어려움에 대해서 알아볼까 합니다. 물론 C++ 에서의 이야기입니다;
- 생성자와 소멸자
그냥 간단히 만든 아래의 예제에서는 정말 간단히 부모 클래스인 CAnimal과 CDog을 한 번씩 생성했다가 지워봅니다.
023: class CAnimal
024: {
025: public:
026: CAnimal()
027: {
028: cout << "Construct animal" << std::endl;
029: };
030:
031: ~CAnimal()
032: {
033: cout << "Destruct animal" << std::endl;
034: };
035:
036: virtual void Say()
037: {
038: cout << "animal" << std::endl;
039: };
040: };
041:
042: class CDog : public CAnimal
043: {
044: public:
045: CDog()
046: {
047: cout << "Construct dog" << std::endl;
048: };
049:
050: ~CDog()
051: {
052: cout << "Destruct dog" << std::endl;
053: };
054:
055: void Say()
056: {
057: cout << "dog" << std::endl;
058: };
059: };
060:
061: void DoSomething()
062: {
063: CAnimal* pAnimal = new CAnimal;
064: delete pAnimal;
065:
066: cout << "--------------------" << std::endl;
067:
068: CDog* pDog = new CDog;
069: delete pDog;
070: }
024: {
025: public:
026: CAnimal()
027: {
028: cout << "Construct animal" << std::endl;
029: };
030:
031: ~CAnimal()
032: {
033: cout << "Destruct animal" << std::endl;
034: };
035:
036: virtual void Say()
037: {
038: cout << "animal" << std::endl;
039: };
040: };
041:
042: class CDog : public CAnimal
043: {
044: public:
045: CDog()
046: {
047: cout << "Construct dog" << std::endl;
048: };
049:
050: ~CDog()
051: {
052: cout << "Destruct dog" << std::endl;
053: };
054:
055: void Say()
056: {
057: cout << "dog" << std::endl;
058: };
059: };
060:
061: void DoSomething()
062: {
063: CAnimal* pAnimal = new CAnimal;
064: delete pAnimal;
065:
066: cout << "--------------------" << std::endl;
067:
068: CDog* pDog = new CDog;
069: delete pDog;
070: }
예상하시듯이 위 코드에서 중요한 부분은 68번째 줄과 69번째 줄 두 줄 뿐입니다. 자식 클래스인 CDog을 생성할 때 부모 클래스와 자식 클래스의 생성자와 소멸자를 각각 한 번씩 호출하게 됩니다. 아래의 순서로 말입니다.
CDog의 생성시
new CDog -> CAnimal 의 생성자 -> CDog 의 생성자 -> 생성 완료!
CDog의 소멸시
delete pDog -> CDog 의 소멸자 -> CAnimal 의 소멸자 -> 소멸 완료!
new CDog -> CAnimal 의 생성자 -> CDog 의 생성자 -> 생성 완료!
CDog의 소멸시
delete pDog -> CDog 의 소멸자 -> CAnimal 의 소멸자 -> 소멸 완료!
만약 부모 클래스인 CAnimal 을 생성할 때, CAnimal 클래스의 멤버 변수들에 대해 어떠한 처리를 해 주어야 한다거나, 마찬가지로 CAnimal 이 없어질 때 멤버 변수들의 메모리를 해제해주는 등의 처리가 필요하다면 위의 코드에서는 모든 기능들이 적절히 수행됩니다. 적어도 아직은요. 위의 코드에 약간의 장난을 쳐 보겠습니다.
023: class CAnimal
024: {
025: public:
026: CAnimal()
027: {
028: cout << "Construct animal" << std::endl;
029: };
030:
031: ~CAnimal()
032: {
033: cout << "Destruct animal" << std::endl;
034: //// CAnimal must free some memories here
035: };
036:
037: virtual void Say()
038: {
039: cout << "animal" << std::endl;
040: };
041: };
042:
043: class CDog : public CAnimal
044: {
045: public:
046: CDog()
047: {
048: cout << "Construct dog" << std::endl;
049: };
050:
051: ~CDog()
052: {
053: cout << "Destruct dog" << std::endl;
054: //// CDog must free some memories here
055: };
056:
057: void Say()
058: {
059: cout << "dog" << std::endl;
060: };
061: };
062:
063: void DoSomething()
064: {
065: CAnimal* pAnimal = new CAnimal;
066: delete pAnimal;
067:
068: cout << "--------------------" << std::endl;
069:
070: CAnimal* pDog = new CDog;
071: delete pDog;
072: }
024: {
025: public:
026: CAnimal()
027: {
028: cout << "Construct animal" << std::endl;
029: };
030:
031: ~CAnimal()
032: {
033: cout << "Destruct animal" << std::endl;
034: //// CAnimal must free some memories here
035: };
036:
037: virtual void Say()
038: {
039: cout << "animal" << std::endl;
040: };
041: };
042:
043: class CDog : public CAnimal
044: {
045: public:
046: CDog()
047: {
048: cout << "Construct dog" << std::endl;
049: };
050:
051: ~CDog()
052: {
053: cout << "Destruct dog" << std::endl;
054: //// CDog must free some memories here
055: };
056:
057: void Say()
058: {
059: cout << "dog" << std::endl;
060: };
061: };
062:
063: void DoSomething()
064: {
065: CAnimal* pAnimal = new CAnimal;
066: delete pAnimal;
067:
068: cout << "--------------------" << std::endl;
069:
070: CAnimal* pDog = new CDog;
071: delete pDog;
072: }
그저 CDog 을 받는 포인터를 부모 클래스의 포인터로 바꾸어 보았습니다. 지난 챕터에서 그러했듯이 말입니다. 당연한 것이지만 생성자나 소멸자도 함수라고 생각한다면 동적 할당에서 벌어지는 문제는 이곳에서도 예외 없이 발생합니다. 무슨 말이냐면
부모 클래스 CAnimal* 로 받은 CDog의 소멸시
delete pDog -> CAnimal 의 소멸자 호출 -> 소멸 완료(?!?)
delete pDog -> CAnimal 의 소멸자 호출 -> 소멸 완료(?!?)
라는 험한 상황이 벌어진다는 이야기입니다. 만약 CDog의 소멸자에서 풀어줘야 했던 메모리가 있다거나, 해야 할 다른 처리가 있었다면 위의 경우는 분명히 Memory leak 이나 여러 알 수 없는 문제를 일으키게 됩니다. 습관처럼 생성자와 소멸자에 여러 기능들을 넣어서 만들곤 하는 프로그래머라면 더더욱 신경 써야 하는 부분이겠지요. 지난 편을 꼼꼼하게 읽고 생각해 본 분이라면 예상했을 이야기이지만 복습 겸 해서 다시 한 번 강조해보기로 합니다 Effective C++ 에서도 초판부터 강조했던 이야기입니다. 놓칠 수 없겠죠. :-)
Class 의 소멸자는 반드시 virtual 로 선언한다!
- 생성 & 소멸시의 또다른 함정
역시 다른 말 필요 없이 다음 코드로 가 보겠습니다. 요점은, Base Class 의 생성자에서 Say 를 한 번 하고 생성하고, 소멸자에서 Finalize 함수를 불러서 정리 작업을 마치고 클래스를 지우고 싶다.. 라는 것입니다.
023: class CAnimal
024: {
025: public:
026: CAnimal()
027: {
028: Say();
029: cout << "Construct animal" << std::endl;
030: };
031:
032: virtual ~CAnimal()
033: {
034: Finalize();
035: cout << "Destruct animal" << std::endl;
036: };
037:
038: virtual void Say()
039: {
040: cout << "animal" << std::endl;
041: };
042:
043: virtual void Finalize()
044: {
045: cout << "Finalize animal" << std::endl;
046: };
047: };
048:
049: class CDog : public CAnimal
050: {
051: public:
052: CDog()
053: {
054: cout << "Construct dog" << std::endl;
055: };
056:
057: ~CDog()
058: {
059: cout << "Destruct dog" << std::endl;
060: };
061:
062: void Say()
063: {
064: cout << "dog" << std::endl;
065: };
066:
067: void Finalize()
068: {
069: cout << "Finalize dog" << std::endl;
070: };
071: };
072:
073: void DoSomething()
074: {
075: CAnimal* pAnimal = new CAnimal;
076: delete pAnimal;
077:
078: cout << "--------------------" << std::endl;
079:
080: CAnimal* pDog = new CDog;
081: delete pDog;
082: }
024: {
025: public:
026: CAnimal()
027: {
028: Say();
029: cout << "Construct animal" << std::endl;
030: };
031:
032: virtual ~CAnimal()
033: {
034: Finalize();
035: cout << "Destruct animal" << std::endl;
036: };
037:
038: virtual void Say()
039: {
040: cout << "animal" << std::endl;
041: };
042:
043: virtual void Finalize()
044: {
045: cout << "Finalize animal" << std::endl;
046: };
047: };
048:
049: class CDog : public CAnimal
050: {
051: public:
052: CDog()
053: {
054: cout << "Construct dog" << std::endl;
055: };
056:
057: ~CDog()
058: {
059: cout << "Destruct dog" << std::endl;
060: };
061:
062: void Say()
063: {
064: cout << "dog" << std::endl;
065: };
066:
067: void Finalize()
068: {
069: cout << "Finalize dog" << std::endl;
070: };
071: };
072:
073: void DoSomething()
074: {
075: CAnimal* pAnimal = new CAnimal;
076: delete pAnimal;
077:
078: cout << "--------------------" << std::endl;
079:
080: CAnimal* pDog = new CDog;
081: delete pDog;
082: }
생성자와 소멸자에서 불린 Say 와 Finalize 는 둘 다 virtual 로 선언되어 있으니까, 아마도 이 코드의 작성자 - 저겠죠; - 는 아래와 같은 의도로 코드를 작성했을 테죠. 하지만 예상과 실제는 다릅니다.
(예상) new CDog 을 하면
CDog::Say 를 호출해서 "dog" 을 출력하고 생성 완료!
(실제) new CDog 을 하면
CAnimal::Say 를 호출해서 "animal" 을 출력하고 생성 완료(?!)
CDog::Say 를 호출해서 "dog" 을 출력하고 생성 완료!
(실제) new CDog 을 하면
CAnimal::Say 를 호출해서 "animal" 을 출력하고 생성 완료(?!)
어째서인지는 앞서 알아보았던 부모 클래스와 자식 클래스의 생성자와 소멸자가 불리던 순서를 떠올려보면 알 수 있습니다. 생성-소멸의 프로세스와 함께 위의 flow를 따라가 보겠습니다.
(실제) new CDog 을 하면
CAnimal::CAnimal() 생성자가 호출
CAnimal::CAnimal() 안에서 Say 를 호출하려고 함.
CAnimal::Say() 가 virtual 이기는 하나,
CDog::Say() 를 호출하려니 아직 CDog은 생성 안 된 상태이므로..
CAnimal::Say() 를 호출
CDogg::CDog() 생성자 호출
생성 완료!
CAnimal::CAnimal() 생성자가 호출
CAnimal::CAnimal() 안에서 Say 를 호출하려고 함.
CAnimal::Say() 가 virtual 이기는 하나,
CDog::Say() 를 호출하려니 아직 CDog은 생성 안 된 상태이므로..
CAnimal::Say() 를 호출
CDogg::CDog() 생성자 호출
생성 완료!
마찬가지로 소멸자가 불렸을 때의 상황도 생각해 보면 다음과 같습니다.
(실제) delete pDog 을 하면
CDog::~CDog() 소멸자가 먼저 호출 - CDog은 사라졌음
CAnimal::~CAnimal() 소멸자 호출
CAnimal::~CAnimal() 안에서 Finalize 를 호출하려고 함.
CAnimal::Finalize 가 virtual 이지만,
CDog::Finalize 를 호출하려니 이미 CDog은 소멸된 상태이므로..
CAnimal::Finalize() 를 호출
소멸 완료!
CDog::~CDog() 소멸자가 먼저 호출 - CDog은 사라졌음
CAnimal::~CAnimal() 소멸자 호출
CAnimal::~CAnimal() 안에서 Finalize 를 호출하려고 함.
CAnimal::Finalize 가 virtual 이지만,
CDog::Finalize 를 호출하려니 이미 CDog은 소멸된 상태이므로..
CAnimal::Finalize() 를 호출
소멸 완료!
이런 상황 때문에 생성자 - 소멸자 내에서 virtual 함수를 사용한다는 것은 일단 눈으로 보기에도 명쾌하지 않은 코드를 만드는 일이기도 하고 - 본인은 위 사항을 정확히 알고 만든 코드라고 해도 누군가는 왜 CDog의 Say 나 Finalize 가 불리지 않느냐에 대해 고민하게 될 지도 모르니까요 - 이것은 버그로 이어질 수도 있습니다. 때문에 아래의 사항을 기억해야 합니다.
Class 의 생성자와 소멸자 내에서는 virtual 함수를 호출하지 않는다!
- 조금 더 생각해보기
그렇다면 다음과 같은 것은 어떨까요? 아예 부모 클래스인 CAnimal 에는 Say() 라는 함수를 순수 가상함수로 선언해 놓고 똑같이 사용해 보는 것입니다.
023: class CAnimal
024: {
025: public:
026: CAnimal()
027: {
028: Say();
029: cout << "Construct animal" << std::endl;
030: };
031:
032: virtual ~CAnimal()
033: {
034: Finalize();
035: cout << "Destruct animal" << std::endl;
036: };
037:
038: virtual void Say() = 0;
039:
040: virtual void Finalize()
041: {
042: cout << "Finalize animal" << std::endl;
043: };
044: };
045:
046: class CDog : public CAnimal
047: {
048: public:
049: CDog()
050: {
051: cout << "Construct dog" << std::endl;
052: };
053:
054: ~CDog()
055: {
056: cout << "Destruct dog" << std::endl;
057: };
058:
059: void Say()
060: {
061: cout << "dog" << std::endl;
062: };
063:
064: void Finalize()
065: {
066: cout << "Finalize dog" << std::endl;
067: };
068: };
069:
070: void DoSomething()
071: {
072: CDog* pDog = new CDog;
073: delete pDog;
074: }
024: {
025: public:
026: CAnimal()
027: {
028: Say();
029: cout << "Construct animal" << std::endl;
030: };
031:
032: virtual ~CAnimal()
033: {
034: Finalize();
035: cout << "Destruct animal" << std::endl;
036: };
037:
038: virtual void Say() = 0;
039:
040: virtual void Finalize()
041: {
042: cout << "Finalize animal" << std::endl;
043: };
044: };
045:
046: class CDog : public CAnimal
047: {
048: public:
049: CDog()
050: {
051: cout << "Construct dog" << std::endl;
052: };
053:
054: ~CDog()
055: {
056: cout << "Destruct dog" << std::endl;
057: };
058:
059: void Say()
060: {
061: cout << "dog" << std::endl;
062: };
063:
064: void Finalize()
065: {
066: cout << "Finalize dog" << std::endl;
067: };
068: };
069:
070: void DoSomething()
071: {
072: CDog* pDog = new CDog;
073: delete pDog;
074: }
결과는? 링크 에러입니다. Base Class 의 생성자/소멸자에서는 자신이 자식 클래스인지 부모인지에 관계 없이 무조건(!) Base Class 기준으로 함수를 호출한다는 걸 예상해볼 수 있는 테스트입니다.
조금만 더 고집을 부려볼까요? 생성자와 소멸자에서 위와 같은 문제가 벌어진다면, CAnimal의 생성자에서 보통의 함수인 Init() 함수를 호출하고, Init() 함수 안에서 순수 가상 함수인 Say() 를 호출하는 겁니다. 아래와 같이요.
023: class CAnimal
024: {
025: public:
026: CAnimal()
027: {
028: Init();
029: cout << "Construct animal" << std::endl;
030: };
031:
032: virtual ~CAnimal()
033: {
034: Finalize();
035: cout << "Destruct animal" << std::endl;
036: };
037:
038: void Init()
039: {
040: Say();
041: };
042:
043: virtual void Say() = 0;
044:
045: virtual void Finalize()
046: {
047: cout << "Finalize animal" << std::endl;
048: };
049: };
050:
051: class CDog : public CAnimal
052: {
053: public:
054: CDog()
055: {
056: cout << "Construct dog" << std::endl;
057: };
058:
059: ~CDog()
060: {
061: cout << "Destruct dog" << std::endl;
062: };
063:
064: void Say()
065: {
066: cout << "dog" << std::endl;
067: };
068:
069: void Finalize()
070: {
071: cout << "Finalize dog" << std::endl;
072: };
073: };
074:
075: void DoSomething()
076: {
077: CDog* pDog = new CDog;
078: delete pDog;
079: }
024: {
025: public:
026: CAnimal()
027: {
028: Init();
029: cout << "Construct animal" << std::endl;
030: };
031:
032: virtual ~CAnimal()
033: {
034: Finalize();
035: cout << "Destruct animal" << std::endl;
036: };
037:
038: void Init()
039: {
040: Say();
041: };
042:
043: virtual void Say() = 0;
044:
045: virtual void Finalize()
046: {
047: cout << "Finalize animal" << std::endl;
048: };
049: };
050:
051: class CDog : public CAnimal
052: {
053: public:
054: CDog()
055: {
056: cout << "Construct dog" << std::endl;
057: };
058:
059: ~CDog()
060: {
061: cout << "Destruct dog" << std::endl;
062: };
063:
064: void Say()
065: {
066: cout << "dog" << std::endl;
067: };
068:
069: void Finalize()
070: {
071: cout << "Finalize dog" << std::endl;
072: };
073: };
074:
075: void DoSomething()
076: {
077: CDog* pDog = new CDog;
078: delete pDog;
079: }
거의 최후의 발악(?) 수준의 노력이고, 결과 역시 최후의 한 방 수준의 것이 등장합니다. 멋지게 빌드되어버리지만, 결과는 Runtime Error !! 말 그대로 돌려 봐야 아는 멋진 버그를 - 게다가 어플리케이션이 죽어 버리는 - 만들어 낸 셈입니다. 물론 이것을 우회할 수 있는 방법이 있기는 합니다만, 앞서 말씀드린 오늘의 요점을 반드시 기억해보도록 합니다.
Class 의 생성자와 소멸자 내에서는 절대로 virtual 함수를 호출하지 않는다!
- Outro
겪다 보면 예전에는 마냥 막연하다고만 생각했던 것들을 '아차!' 하며 뇌리에 떠올리게 될 때가 있습니다. 이 부분은 몇 년 전 제게 너무나 쿵(!) 했던 내용이었답니다 시간이 난다면 가상 함수를 썼을 때와 그렇지 않을 때의 메모리상의 주소 차이나, 함수 콜 시간의 차이 등도 한 번 정리해서 적어보았으면 좋겠군요. 아, 타입 캐스팅도 좋은 주제가 되겠네요.



Adidas NBA Jerseys , Cheap NBA Basketball Jerseys ,Wholesale NBA Basketball Jerseys ,
Adidas NBA Basketball Jerseys ,Throwback NBA Jerseys ,Authentics NBA Jerseys
Atlanta Hawks Jerseys , Boston Celtics Jerseys , Kevin Garnett Jerseys , Charlotte Bobcats Jerseys
chicago bulls Jerseys , Cleveland Cavaliers Jerseys ,Lebron James Jerseys ,Shaq Oneal Jerseys ,
Dallas Mavericks Jerseys ,Jason Kidd Jerseys , Denver Nuggets Jerseys , Detroit Pistons Jerseys
Houston Rockets Jerseys , Los Angeles Clippers Jerseys , Los Angeles Lakers Jerseys ,
Kobe bryants Lakers , Miami Heat Jerseys , Dwayne Wade Jerseys , Minnesota Timberwolves Jerseys ,
New Orleans Hornets Jerseys ,Chris Paul Jerseys , Orlando Magic Jerseys ,
vince carter Jerseys ,Dwight Howard Jerseys , Phoenix Suns Jerseys , Steve Nash Jerseys ,
Portland TrailBlazers Jerseys , Roy Jerseys , Sacramento Kings Jerseys , San Antonio Spurs Jerseys ,
Tim Duncan Jerseys , Utah Jazz Jerseys , Deron Williams Jerseys , Washington Wizards jerseys
Cheap basketball jerseys , Wholesale basketball jerseys , Discount basketball jerseys , Youth basketball jerseys
NBA basketball jerseys Sale ,NBA basketball jerseys for Sale