Спадщина
Вся суть спадкування полягає у спільному використанні загального інтерфейсу та протоколу між багатьма різними реалізаціями, таким чином, що екземпляр похідного класу може трактуватися ідентично будь-якому іншому екземпляру з будь-якого іншого похідного типу.
У C ++ успадкування також приносить із собою деталі реалізації, маркування (або не маркування) деструктора як віртуального є однією з таких деталей реалізації.
Функція Прив’язка
Тепер, коли викликається функція або будь-який її особливий випадок, як конструктор чи деструктор, компілятор повинен вибрати, яку саме функцію мав на увазі. Тоді він повинен генерувати машинний код, що відповідає цьому наміру.
Найпростішим способом для цього було б вибрати функцію під час компіляції та видавати достатньо машинного коду, щоб незалежно від будь-яких значень, коли цей фрагмент коду виконується, він завжди запускає код для функції. Це чудово працює, крім спадщини.
Якщо у нас є базовий клас з функцією (може бути будь-яка функція, включаючи конструктор або деструктор) і ваш код називає функцію на ньому, що це означає?
Виходячи зі свого прикладу, якщо ви зателефонували, initialize_vector()
компілятор повинен вирішити, чи дійсно ви хочете викликати реалізацію, знайдену в Base
, або реалізацію, знайдену в Derived
. Є два способи вирішити це:
- Перший - це вирішити, що, оскільки ви телефонували від
Base
типу, ви мали на увазі реалізацію в Base
.
- Другий - вирішити, що тому, що тип виконання, який зберігається у
Base
набраному значенні, може бути Base
, або Derived
що рішення про те, який дзвінок робити, повинно прийматися під час виконання під час виклику (кожен раз, коли він викликається).
У цьому місці компілятор плутається, обидва варіанти однаково дійсні. Це коли virtual
потрапляє в суміш. За наявності цього ключового слова компілятор вибирає варіант 2, затримуючи рішення між усіма можливими реалізаціями, поки код не запуститься з реальним значенням. Якщо це ключове слово відсутнє, компілятор вибирає варіант 1, оскільки це інакше нормальна поведінка.
У випадку виклику віртуальної функції компілятор може все ж вибрати варіант 1. Але лише якщо це може довести, що це завжди так.
Конструктори та деструктори
То чому б ми не вказали віртуального конструктора?
Як інтуїтивніше, як компілятор вибиратиме між однаковими реалізаціями конструктора для Derived
та Derived2
? Це досить просто, не може. Немає попереднього значення, з якого компілятор може дізнатися, що було дійсно призначено. Немає попереднього значення, оскільки це завдання конструктора.
То чому нам потрібно вказати віртуального деструктора?
Більш інтуїтивно, як компілятор вибиратиме між реалізаціями для Base
та Derived
? Вони є лише функціональними дзвінками, тому поведінка виклику функції відбувається. Без оголошеного віртуального деструктора компілятор вирішить прив’язати безпосередньо до Base
деструктора незалежно від типу значень часу виконання.
У багатьох компіляторах, якщо похідне не оголошує жодних членів даних і не успадковує від інших типів, поведінка у ~Base()
заповіті буде придатною, але це не гарантується. Це спрацювало б лише випадково, подібно до того, щоб стояти перед вогнемет, який ще не був запалений. Ви деякий час добре.
Єдиний правильний спосіб оголошення будь-якого базового або інтерфейсного типу в C ++ - це оголошення віртуальної деструктора, щоб правильний деструктор викликався для будь-якого даного екземпляра ієрархії типу цього типу. Це дозволяє функції, що володіє найбільш відомими примірниками, правильно очистити цей екземпляр.
~derived()
делегування деструктора vec. Як варіант, ви припускаєте, щоunique_ptr<base> pt
знав би похідний деструктор. Без віртуального методу це не може бути. Хоча унікальному_ptr може бути надана функція видалення, яка є параметром шаблону без будь-якого представлення часу виконання, і ця функція не використовує цього коду.