Чи можливо для успадкованого класу реалізувати віртуальну функцію з іншим типом повернення (не використовуючи шаблон як повернення)?
Відповіді:
У деяких випадках так, це дозволено, щоб похідний клас перевизначав віртуальну функцію, використовуючи інший тип повернення, доки тип повернення є коваріантним з вихідним типом повернення. Наприклад, розглянемо наступне:
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 ++ вважає це абсолютно новою функцією, тому ви починаєте перевантажувати та приховувати. Докладніше про цю тему див. У розділі Коваріація та контраваріація (інформатика) у Вікіпедії.
Реалізація похідного класу віртуальної функції може мати коваріантний тип повернення .