Подтвердить что ты не робот

Использование параметров типа подкласса в виртуальных функциях

У меня есть этот кусок кода (ухитрившийся от моей реальной проблемы)

Он не может скомпилировать, жалуясь, что ExtendsB не реализует B::Run(A* a). Однако у него нет проблем с пониманием расширения до A* Run();

class A { };

class ExtendsA : public A { };

class B
{
public:
    virtual ~B(){}  
    virtual void Run(A* a) = 0;
    virtual A* Run() = 0;
};

class ExtendsB : public B
{
public:
    virtual ~ExtendsB(){}

    // Not OK! It does not see it as an implementation of 
    // virtual void Run(A* a) = 0;
    virtual void Run(ExtendsA* ea) {}; 
    virtual ExtendsA* Run() { return new ExtendsA(); }; // OK
};

Почему С++ позволяет изменить тип возвращаемого значения на подкласс, но не тип параметра?

Это хорошее обоснование или просто пропущенная точка в спецификациях языка?

4b9b3361

Ответ 1

Почему С++ позволяет изменить тип возвращаемого значения на подкласс, но не тип параметра?

Стандарт С++ позволяет использовать тип возвращаемого значения Covariant при обмане виртуальных функций, но не позволяет изменять параметры функции. И да, за этим есть хорошее обоснование.

Обоснование:

Переопределение по существу означает, что либо метод базового класса, либо метод класса Derived будут вызываться во время выполнения в зависимости от фактического объекта, на который указывает указатель.
Это означает, что:
i.e: "Каждый экземпляр, в котором может быть вызван метод базового класса, может быть заменен вызовом метода Derived класса без каких-либо изменений кода вызова".

Если вышеприведенное правило не было на месте, оно оставило бы окно, чтобы сломать существующий код, добавив новую функциональность (новые производные классы).

Если у вас есть прототип функции в производном классе, который отличается от параметров виртуальной функции w.r.t базового класса, то функция не переопределяет функцию базового класса, так как вышеприведенное правило разбивается.

Тем не менее, возвращаемые типы Covariant не нарушают это правило, потому что восходящее событие происходит неявно, а указатель базового класса всегда может указывать на объект производного класса без какого-либо кастинга, поэтому стандарт применяет это условие ковариантных типов возврата к типам возвращаемых данных.

Ответ 2

Ошибка довольно ясна: вам нужно void Run(A*) в вашем классе ExtendedB, но у вас этого нет. Все, что у вас есть, это void Run(ExtendedA*), но это не то же самое: базовый класс promises, чтобы принять любой A -pointer, но ваш ExtendedB::Run является более строгим и принимает только узкое подмножество.

(Вы вводите в заблуждение ковариацию и контравариантность, но это не относится к С++, поскольку С++ не допускает контравариантных переопределений.)

Ответ 3

Это просто два разных типа, что делает его двумя различными функциями с двумя различными сигнатурами.

В общем случае, если вы используете компилятор, который понимает С++ 11, вы должны использовать ключевое слово override для функций, предназначенных для переопределения другой функции. В вашем случае ошибка стала очевидной из-за абстрактного базового класса, но в других случаях такая ошибка может вызвать много отладки...

Ответ 4

Специализация типа возврата делает подкласс более строгим - он будет действовать как A. Однако ограничение метода Run на принятие только подкласса исходного аргумента делает B не действительным как A.

Наследование моделирует отношение is-a. Ваш код нарушает принцип замены Лискова.