Модификатор override
Модификатор override появился в стандарте C++11 и используется при описании методов дочернего класса.
Модификатор override следует писать для тех методов, которые по задумке программиста являются переопределенными методами базового класса. Данный модификатор пишется после имени метода. В заголовочном файле описание метода с использованием override выглядит так:
class B : public A
{
public:
virtual const char* getVersion() override;
};
В реализации (т. е. в *.cpp-файле), модификатор override не пишется, а используется только при определении (т. е. в заголовочном файле *.h).
Модификатор override позволяет компилятору следить за тем, чтобы метод, помеченный этим модификатором действительно переопределял метод базового класса. В языке C++ метод дочернего класса будет переопределять метод базового класса только в том случае, если его сигнатура полностью совпадает с сигнатурой базового класса (одно исключение из этого правила есть, оно написано ниже). В больших проектах, или просто по запарке, можно не обратить внимание, например, на то, что в одной сигнатуре используется обычный указатель, а в другой константный указатель. И программист будет думать что он переопределил метод, а на самом деле переопределения не произошло. Чтобы избежать таких ошибок, рекомендуется для каждого метода, который переопределяет базовый метод, писать модификатор override.
Модификатор override и ковариантный тип возврата
Есть один случай, когда тип возврата переопределения может не совпадать с типом возврата виртуального метода родительского класса, но при этом оставаться переопределением:
Если базовый виртуальный метод возвращает указатель или ссылку на класс, то переопределенные методы могут возвращать указатель или ссылку на свой собственный класс. Другими словами, в переопределенных методах в качестве возвращаемого типа можно вместо указателя на родительский класс возвращать указатель на дочерний класс. Это называется ковариантным типом возврата.
Например:
#include <iostream>
class Parent
{
public:
// Этот метод getThis() возвращает указатель на класс Parent
virtual Parent* getThis()
{
std::cout << "called Parent::getThis()\n";
return this;
}
void printType()
{
std::cout << "returned a Parent\n";
}
};
class Child : public Parent
{
public:
// Обычно, типы возврата переопределений и виртуальных функций
// родительского класса должны совпадать.
// Однако, поскольку Child наследует класс Parent, следующий метод
// может возвращать Child* вместо Parent*
virtual Child* getThis()
{
std::cout << "called Child::getThis()\n";
return this;
}
void printType()
{
std::cout << "returned a Child\n";
}
};
int main()
{
Child ch;
Parent *p = &ch;
// Вызывается Child::getThis(), возвращается Child*, вызывается Child::printType
ch.getThis()->printType();
// Вызывается Child::getThis(), возвращается Parent*, вызывается Parent::printType
p->getThis()->printType();
}
Результат выполнения программы:
called Child::getThis()
returned a Child
called Child::getThis()
returned a Parent
Внимание! Некоторые старые компиляторы могут не поддерживать ковариантные типы возврата.
В примере, приведенном выше, сначала вызывается ch.getThis(). Поскольку ch является объектом класса Child, то вызывается Child::getThis(), который возвращает Child*. Этот Child* затем используется для вызова невиртуальной функции Child::printType().
Затем выполняется p->getThis(). Переменная p является указателем класса Parent на объект ch класса Child. Parent::getThis() — это виртуальная функция, поэтому вызывается переопределение Child::getThis(). Хотя Child::getThis() и возвращает Child*, но, поскольку родительская часть объекта возвращает Parent*, возвращаемый Child* преобразовывается в Parent*. И, таким образом, вызывается Parent::printType().
Тип Child* в данном примере будет возвращен только в том случае, если будем вызывать метод getThis() с объектом класса Child.
Модификатор final
Модификатор final используется для двух случаев:
- чтобы пометить метод базового класса, который не должен переопределяться;
- чтобы пометитить весь класс что он не должен наследоваться.
Если пользователь пытается переопределить метод или наследовать класс с модификатором final, то компилятор выдаст ошибку.
Модификатор final, так же как модификатор override, пишется после имени метода или имени класса.
Описание неизменяемого метода:
virtual const char* getVersion() override final;
Описание неизменяемого класса:
class B final : public A
{
public:
virtual const char* getVersion() override;
};
При сборке программы компилятор проверяет, не происходит ли попытки переопределить "финализированный" метод или класс, и если находит, то генерирует ошибку времени компиляции.