이 답변으로 가설을 세울 수 있으므로 명확성을 기하기 위해 더 간단하고 지구 설명에 이르기까지 설명하려고합니다.
객체 지향 디자인의 기본 관계는 IS-A와 HAS-A입니다. 나는 그것들을 만들지 않았다. 그것이 그들이 부르는 것입니다.
IS-A는 특정 개체가 클래스 계층에서 그 위에있는 클래스임을 식별합니다. 바나나 오브젝트는 과일 클래스의 서브 클래스 인 경우 과일 오브젝트입니다. 이것은 과일 클래스를 사용할 수있는 곳이면 어디서나 바나나를 사용할 수 있다는 것을 의미합니다. 그러나 그것은 반사적이지 않습니다. 특정 클래스가 필요한 경우 특정 클래스를 기본 클래스로 대체 할 수 없습니다.
Has-a는 객체가 복합 클래스의 일부이며 소유권 관계가 있음을 나타냅니다. 그것은 C ++에서 그것이 멤버 객체라는 것을 의미하며, 따라서 자신을 파괴하기 전에 그것을 처분하거나 소유권을 넘겨주는 소유 클래스에 있습니다.
이 두 개념은 c ++와 같은 다중 상속 모델보다 단일 상속 언어에서 구현하기가 쉽지만 규칙은 본질적으로 동일합니다. Fruit 클래스 포인터를 취하는 함수에 바나나 클래스 포인터를 전달하는 것과 같이 클래스 ID가 모호 할 때 복잡성이 발생합니다.
가상 기능은 첫째 런타임입니다. 다형성의 일부로, 실행중인 프로그램에서 호출 될 때 실행할 함수를 결정하는 데 사용됩니다.
virtual 키워드는 클래스 ID에 대한 모호성이있는 경우 특정 순서로 함수를 바인드하는 컴파일러 지시문입니다. 가상 함수는 항상 부모 클래스 (내가 아는 한)에 있으며 멤버 함수를 이름에 바인딩하는 것은 먼저 서브 클래스 함수와 부모 클래스 함수를 사용해야합니다.
Fruit 클래스에는 기본적으로 "NONE"을 반환하는 가상 함수 color ()가있을 수 있습니다. Banana 클래스 color () 함수는 "YELLOW"또는 "BROWN"을 반환합니다.
그러나 Fruit 포인터를 취하는 함수가 Banana 클래스에 color ()를 호출하면 어떤 color () 함수가 호출됩니까? 이 함수는 일반적으로 Fruit 객체에 대해 Fruit :: color ()를 호출합니다.
그것은 시간의 99 %가 의도 한 것이 아닐 것입니다. 그러나 Fruit :: color ()가 virtual로 선언 된 경우 호출시 올바른 color () 함수가 Fruit 포인터에 바인딩되므로 Banana : color ()가 객체에 대해 호출됩니다. 런타임은 포인터가 과일 클래스 정의에서 가상으로 표시 되었기 때문에 포인터가 가리키는 객체를 확인합니다.
이것은 서브 클래스에서 함수를 재정의하는 것과 다릅니다. 이 경우 Fruit 포인터는 Fruit :: color ()를 호출합니다.
이제 "순수 가상 기능"이라는 아이디어가 나옵니다. 순도는 그것과 관련이 없기 때문에 다소 불행한 문구입니다. 그것은 기본 클래스 메소드가 호출되지 않도록 의도되었음을 의미합니다. 실제로 순수한 가상 함수를 호출 할 수 없습니다. 그러나 여전히 정의되어 있어야합니다. 함수 서명이 존재해야합니다. 많은 코더가 완전성을 위해 빈 {{} 구현을 수행하지만 컴파일러는 내부적으로 생성합니다. 이 경우 포인터가 Fruit 일지라도 함수가 호출되면 color ()의 유일한 구현이므로 Banana :: color ()가 호출됩니다.
이제 퍼즐의 마지막 조각 : 생성자와 소멸자.
순수한 가상 생성자는 완전히 불법입니다. 그냥 나왔어
그러나 순수 가상 소멸자는 기본 클래스 인스턴스 생성을 금지하려는 경우 작동합니다. 기본 클래스의 소멸자가 순수 가상 인 경우 하위 클래스 만 인스턴스화 할 수 있습니다. 컨벤션은 0에 할당하는 것입니다.
virtual ~Fruit() = 0; // pure virtual
Fruit::~Fruit(){} // destructor implementation
이 경우 구현을 작성해야합니다. 컴파일러는 이것이 당신이하고있는 일임을 알고 올바르게 수행하는지 확인하거나 컴파일 해야하는 모든 기능에 연결할 수 없다고 강력하게 불평합니다. 클래스 계층을 모델링하는 방법에 대한 올바른 길을 찾지 못하면 오류가 혼동 될 수 있습니다.
따라서이 경우 Fruit 인스턴스를 만들 수 없지만 Banana 인스턴스를 만들 수 있습니다.
Banana의 인스턴스를 가리키는 Fruit 포인터를 삭제하면 먼저 Banana :: ~ Banana ()를 호출 한 다음 항상 Fuit :: ~ Fruit ()을 호출합니다. 서브 클래스 소멸자를 호출 할 때는 기본 클래스 소멸자를 따라야합니다.
나쁜 모델입니까? 디자인 단계에서는 더 복잡하지만, 정확한 링크가 런타임에 수행되고 서브 클래스 기능이 정확히 어떤 서브 클래스에 액세스되는지 모호한 곳에서 수행되도록 보장 할 수 있습니다.
C ++을 작성하여 일반적인 포인터 나 모호한 포인터가없는 정확한 클래스 포인터 만 전달하면 가상 함수가 실제로 필요하지 않습니다. 그러나 (Apple Banana Orange ==> Fruit에서와 같이) 유형의 런타임 유연성이 필요한 경우 중복 코드가 적어 기능이 더 쉽고 다양합니다. 더 이상 각 과일 유형에 대해 함수를 작성할 필요가 없으며 모든 과일이 고유 한 올바른 함수로 color ()에 응답한다는 것을 알고 있습니다.
이 긴 설명이 혼란을 일으키는 것이 아니라 개념을 강화시키기를 바랍니다. 거기에는 볼만한 좋은 예가 많이 있으며, 충분히 살펴보고 실제로 실행하고 엉망으로 만들면 얻을 수 있습니다.