CodeNote
Coding Note
CodeNote
전체 방문자
오늘
어제
  • 전체 보기 (35)
    • C++ (33)
      • Modern C++ (12)
      • Modern C++ STL (0)
      • Thread (16)
      • Thread (Async) (5)
    • 디자인패턴 (0)
    • Algorithm (2)
    • Electron (0)
    • Python (0)

블로그 메뉴

  • 홈
  • Github
  • 태그
  • 방명록

공지사항

인기 글

태그

  • Free
  • mutex
  • 자료구조
  • LOCK
  • C++ #Memory
  • C++

최근 댓글

최근 글

티스토리

hELLO · Designed By 정상우.
CodeNote

Coding Note

[C++] 가상함수 원리
C++/Modern C++

[C++] 가상함수 원리

2022. 10. 24. 10:11

Virtual Function (가상 함수)

class Animal
{
public:
	Animal() { std::cout << "Animal Constructor" << std::endl; }
	~Animal() { std::cout << "Animal Destructor" << std::endl; }
};

class Cat : public Animal
{
public:
	Cat() { std::cout << "Cat Constructor" << std::endl; }
	~Cat() { std::cout << "Cat Destructor" << std::endl; }
};

int main()
{
	Cat* catPtr = new Cat();
	delete catPtr;
}

출력 결과

모든 상속 관계에서는 Base Class의 Constructor가 가장 먼저 실행이 되고 Base Class의 Destructor가 가장 마지막에 실행이 된다.  

 

int main()
{
	Animal* polyCat = new Cat();
	delete polyCat;
}

출력 결과

Cat 오브젝트를 생성한 후  Animal 포인터로 받았다. Cat 클래스의 Destructor가 실행되지 않았다. 사실 상속을 사용하는데 있어서 Base Class의 Destructor는 virtual public 이거나 protected로 선언되어야한다.

 

protected로 선언이 되면 Base Class를 오브젝트로 만들지 않겠다는 의미이기 때문에 이러한 특성이 필요할 때만 사용이 되고 거의 모든 경우에는 virtual public으로 선언한다.

 

class Animal
{
public:
	Animal() { std::cout << "Animal Constructor" << std::endl; }
	virtual ~Animal() { std::cout << "Animal Destructor" << std::endl; }
};

class Cat : public Animal
{
public:
	Cat() { std::cout << "Cat Constructor" << std::endl; }
	~Cat() { std::cout << "Cat Destructor" << std::endl; }
};

출력 결과

Destructor에 virtual 을 추가하였더니 정상적으로 Cat 클래스의 Destructor가 호출이 되었다.

 

가상 함수 원리

class Animal
{
public:
	virtual void Speak()
	{
		std::cout << "Animal" << std::endl;
	}
private:
	double height; // 8 Bytes
};

class Cat : public Animal
{
public:
	void Speak() override
	{
		std::cout << "meow" << std::endl;
	}
private:
	double weight; // 8 Bytes
};

Animal 클래스는 8 Bytes이고 Cat 클래스는 16 Bytes를 갖게 된다. 그런데 여기서 speak에 virtual을 넣고 Cat에서 override를 하게 되면 각 클래스의 사이즈가 8 Bytes씩 추가된다. 즉, Animal은 16 Bytes가 되고 Cat은 24 Bytes가 된다.


이는 주소 정보가 추가된 것인데 그림으로 나타내보면,

 


Animal과 Cat은 어떠한 포인터 정보를 가지게 되는데 각각의 virtual table을 가리키게 된다. Animal virtual table에는 animal의 speak 함수를 가리킬 것이고 Cat virtual table은 cat의 speak 함수를 가리킬 것이다. 이러한 virtual table은 Heap이나 Stack이 아닌 Code(코드)영역에 위치하게 된다.

더보기

Code(코드) 영역

프로세스가 실행할 코드와 매크로 상수가 기계어의 형태로 저장된 공간이다. 컴파일 타임에 결정되고 중간에 코드를 바꿀 수 없게 Read-Only 로 지정되어 있다.

 

다음 2가지 예를 보자.

int main()
{
	Animal* polyAnimal = new Cat();
	polyAnimal->Speak();
	delete polyAnimal;
}

polyAnimal은 Heap에 있는 Cat 오브젝트를 가리킬 것이다. 그리고 당연하게도 Cat 오브젝트를 만들었기 때문에 Virtual Table의 주소를 가리키는 포인터는 Cat의 Virtual Table을 가리킨다. 이 후 polyAnimal에서 speak() 함수를 호출하게 되면 포인터가 가리키는 virtual table을 따라간다. virtual Table은 Cat의 speak 함수를 찾아가기 때문에 meow를 출력하게 된다.

 

int main()
{
	Animal* polyAnimal = new Animal();
	polyAnimal->Speak();
	delete polyAnimal;
}

Cat이 아닌 Animal을 생성하게 되면 Heap 상의 오브젝트가 Animal이 되고 Animal의 Virtual Table을 가리키게 된다.

 

이를 통해, Base Class Destructor는 virtual로 만들어야한다라는 의미를 확인해볼 수 있다. Destructor도 하나의 함수이기 때문에 Animal의 virtual table 안에 포인터 정보가 있고 Animal 클래스의 destructor를 가리키게 된다.

 

Base Class와 Derived Class는 상속 관계이고 Destructor 함수는 삭제 시 호출되는 특별한 함수로서 Base Class의 Destructor를 자동 호출한다. 따라서, Cat을 가리키는 PolyAnimal이 삭제되면 현재 virtual table은 Cat virtual table이기 때문에 ~Cat() 함수가 호출되고 그 안에서 ~Animal()을 호출하게 되는 것이다.

'C++ > Modern C++' 카테고리의 다른 글

[C++] Union  (0) 2022.11.04
[C++17] std::optional  (0) 2022.10.31
[C++] attributes(속성)  (0) 2022.10.13
[C++] Perfect forwarding  (0) 2022.10.05
[C++] constexpr  (0) 2022.10.05
    'C++/Modern C++' 카테고리의 다른 글
    • [C++] Union
    • [C++17] std::optional
    • [C++] attributes(속성)
    • [C++] Perfect forwarding
    CodeNote
    CodeNote
    기록 블로그

    티스토리툴바