Чи можливо для успадкованого класу реалізувати віртуальну функцію з іншим типом повернення (не використовуючи шаблон як повернення)?
Відповіді:
У деяких випадках так, це дозволено, щоб похідний клас перевизначав віртуальну функцію, використовуючи інший тип повернення, доки тип повернення є коваріантним з вихідним типом повернення. Наприклад, розглянемо наступне:
class Base {
public:
virtual ~Base() {}
virtual Base* clone() const = 0;
};
class Derived: public Base {
public:
virtual Derived* clone() const {
return new Derived(*this);
}
};
Тут Base
визначається чисто віртуальна функція, що називається, clone
яка повертає a Base *
. У похідній реалізації цю віртуальну функцію замінено, використовуючи тип повернення Derived *
. Хоча тип повернення не такий, як у базовому, це абсолютно безпечно, тому що в будь-який час, коли ви пишете
Base* ptr = /* ... */
Base* clone = ptr->clone();
Виклик clone()
завжди повертає покажчик на Base
об’єкт, оскільки навіть якщо він повертає a Derived*
, цей вказівник неявно конвертується в a, Base*
і операція чітко визначена.
Загальніше, тип повернення функції ніколи не вважається частиною її підпису. Ви можете замінити функцію-член будь-яким типом повернення, якщо тип повернення є коваріантним.
Base*
на long
і Derived*
з int
(або навпаки, не має значення). Це не спрацює.
Так. Типи повернення можуть бути різними, якщо вони є коваріантними . Стандарт С ++ описує це так (§10.3 / 5):
Тип повернення функції, що перевизначає функцію, повинен бути або ідентичним типу повернення функції, що перевизначується, або коваріанта класам функцій. Якщо функція
D::f
замінює функціюB::f
, тип повернення функцій є коваріантним, якщо задовольняє наступним критеріям:
- обидва є вказівниками на класи або посиланнями на класи 98)
- клас у типі повернення
B::f
є тим самим класом, що і клас у типі поверненняD::f
або, є однозначним прямим або непрямим базовим класом класу у типі поверненняD::f
та доступний уD
- обидва вказівники або посилання мають однакову cv-кваліфікацію, а тип класу у типі повернення
D::f
має таку ж cv-кваліфікацію, як або менше cv-кваліфікацію, ніж тип класу у типі поверненняB::f
.
Виноска 98 зазначає, що "багаторівневі вказівники на класи або посилання на багаторівневі вказівники на класи не дозволяються".
Коротше кажучи, якщо D
є підтипом B
, то тип повернення функції в D
повинен бути підтипом типу повернення функції в B
. Найпоширеніший приклад - це випадки, коли типи повернення самі базуються на D
та B
, але не обов’язково. Розглянемо це, де ми розділяємо дві ієрархії типів:
struct Base { /* ... */ };
struct Derived: public Base { /* ... */ };
struct B {
virtual Base* func() { return new Base; }
virtual ~B() { }
};
struct D: public B {
Derived* func() { return new Derived; }
};
int main() {
B* b = new D;
Base* base = b->func();
delete base;
delete b;
}
Причина, по якій це працює, полягає в тому, що будь-який абонент, який телефонує func
, очікує Base
покажчика. Base
Підійде будь-який вказівник. Отже, якщо D::func
обіцяє завжди повертати Derived
покажчик, то він завжди буде задовольняти контракт, викладений класом предка, оскільки будь-який Derived
покажчик може неявно перетворюватися на Base
покажчик. Таким чином, абоненти завжди отримають те, що очікують.
Окрім того, що дозволяють змінювати тип повернення, деякі мови також дозволяють змінювати типи параметрів функції заміщення. Коли вони це роблять, їм, як правило, потрібно бути противаріантними . Тобто, якщо B::f
приймає a Derived*
, тоді D::f
буде дозволено прийняти a Base*
. Нащадкам дозволено бути вільнішими в тому, що вони приймуть, і жорсткішими у тому, що вони повернуть. С ++ не допускає противаріантності типу параметра. Якщо ви зміните типи параметрів, C ++ вважає це абсолютно новою функцією, тому ви починаєте перевантажувати та приховувати. Докладніше про цю тему див. У розділі Коваріація та контраваріація (інформатика) у Вікіпедії.
Реалізація похідного класу віртуальної функції може мати коваріантний тип повернення .