Ви натрапляєте на гіпотетику з цими відповідями, тому я спробую зробити більш зрозуміле і зрозуміле пояснення задля ясності.
Основні співвідношення об'єктно-орієнтованого дизайну два: IS-A і HAS-A. Я цього не придумував. Саме так їх називають.
IS-A вказує на те, що конкретний об'єкт ідентифікує як клас класу, який знаходиться над ним у ієрархії класів. Банановий об’єкт - це фруктовий об’єкт, якщо він є підкласом фруктового класу. Це означає, що де завгодно фруктовий клас можна використовувати банан. Однак це не рефлексивно. Ви не можете замінити базовий клас для конкретного класу, якщо для цього вимагається певний клас.
Has-вказав, що об’єкт є частиною складеного класу і що існує відносини власності. Це означає в C ++, що він є об'єктом-членом, і, як такий, onus належить класу володіння, щоб розпоряджатися ним або передавати право власності, перш ніж знищити себе.
Ці два поняття легше реалізувати в мовах одного успадкування, ніж у моделі множинного успадкування на зразок c ++, але правила по суті однакові. Ускладнення виникає тоді, коли ідентичність класу неоднозначна, наприклад, передача покажчика класу Banana у функцію, яка приймає вказівник класу Fruit.
Віртуальні функції - це, по-перше, річ, що виконується. Він є частиною поліморфізму в тому, що він використовується для того, щоб визначити, яку функцію виконувати в той момент, коли вона викликається в запущеній програмі.
Віртуальне ключове слово - це директива компілятора для прив’язки функцій у певному порядку, якщо є неоднозначність щодо ідентичності класу. Віртуальні функції завжди є в батьківських класах (наскільки я знаю) і вказують компілятору, що прив'язка функцій-членів до їх імен повинна відбуватися спочатку за допомогою функції підкласу, а після цього функція батьківського класу.
Клас Fruit може мати віртуальний колір функції (), який повертає "NONE" за замовчуванням. Функція кольору () класу Banana повертає "ЖОВТИЙ" або "РІЗНИЙ".
Але якщо функція, що приймає вказівник Fruit, викликає color () для надісланого йому класу Banana - який колір () функція буде викликаний? Функція зазвичай називає Fruit :: color () для об'єкта Fruit.
Це 99% часу не було б тим, що було призначено. Але якщо Fruit :: color () було оголошено віртуальним, тоді для об'єкта буде викликано Banana: color (), оскільки правильна функція color () буде прив’язана до вказівника Fruit під час виклику. Виконання буде перевіряти, на який об’єкт вказує вказівник, оскільки він був визначений віртуальним у визначенні класу Fruit.
Це відрізняється від перекриття функції підкласу. У такому випадку вказівник Fruit буде називати Fruit :: color (), якщо все, що він знає, це те, що це IS-A вказівник на Fruit.
Тож зараз до ідеї "чистої віртуальної функції" приходить. Це досить невдала фраза, оскільки чистота не має нічого спільного. Це означає, що призначено, щоб метод базового класу ніколи не називався. Дійсно чисту віртуальну функцію назвати не можна. Однак це все ж має бути визначено. Підпис функції повинен існувати. Багато кодерів роблять порожню реалізацію {} для повноти, але компілятор генерує її внутрішньо, якщо ні. У тому випадку, коли функція викликається, навіть якщо вказівник на Fruit, буде називатися Banana :: color (), оскільки це єдина реалізація color ().
Тепер заключний фрагмент головоломки: конструктори та деструктори.
Чисті віртуальні конструктори є незаконними, повністю. Це просто вийшло.
Але чисті віртуальні деструктори спрацьовують у випадку, якщо ви хочете заборонити створення екземпляра базового класу. Тільки підкласи можуть бути ініційовані, якщо деструктор базового класу є чисто віртуальним. конвенція полягає у призначенні його 0.
virtual ~Fruit() = 0; // pure virtual
Fruit::~Fruit(){} // destructor implementation
У цьому випадку вам потрібно створити реалізацію. Компілятор знає, що це ви робите, і переконуєсь, що ви робите це правильно, або він сильно скаржиться, що не може зв’язати всі функції, необхідні для його складання. Помилки можуть бути заплутаними, якщо ви не на правильному шляху щодо моделювання ієрархії класів.
Тож вам забороняється створювати екземпляри Fruit, але дозволяти створювати екземпляри Banana.
Заклик видалити вказівник Fruit, який вказує на екземпляр Banana, спочатку зателефонує Banana :: ~ Banana (), а потім викликає Fuit :: ~ Fruit (). Тому що незалежно від того, коли ви викликаєте деструктор підкласу, деструктор базового класу повинен слідувати.
Це погана модель? Так, на стадії проектування це складніше, але це може забезпечити правильне з'єднання під час виконання та функцію підкласу виконувати там, де існує неоднозначність щодо того, до якого саме підкласу звертається.
Якщо ви пишете C ++ так, щоб ви обходили лише точні вказівники класу без загальних чи неоднозначних покажчиків, то віртуальні функції насправді не потрібні. Але якщо вам потрібна гнучкість у виконанні типів (як у Apple Banana Orange ==> Fruit), функції стають легшими та універсальнішими з менш зайвим кодом. Вам більше не потрібно писати функції для кожного виду фруктів, і ви знаєте, що кожен фрукт буде відповідати кольору () своєю правильною функцією.
Я сподіваюся, що це завзято пояснення зміцнює концепцію, а не плутає речі. Є багато хороших прикладів для того, щоб подивитися і подивитися достатньо і насправді запустити їх і возитися з ними, і ви отримаєте це.