Почему ковариация не допускается с абстрактным классом?

#c #interface #abstract-class #covariance

Вопрос:

 class GameObject {
public:
    virtual ~GameObject() {}
};

class Player: public GameObject {};

struct IGameController {
    virtual GameObject* GetPlayer() = 0;
};

class CameraOnlyController : public IGameController {
public:
    GameObject* GetPlayer() override { return nullptr; }
};

class PlayerController : public IGameController {
public:
    PlayerController() { player = new Player(); }
    ~PlayerController() { delete player; }
    Player* GetPlayer() override { return player; }
private:
    Player* player;
};

int main() {
    IGameController* playerController = new PlayerController();
    Player* player = playerController->GetPlayer();     // error: a value of type "GameObject *" cannot be used to initialize an entity of type "Player *"
    delete playerController;
}
 

Он компилируется, если я специально изменю интерфейс контроллера на PlayerController

 PlayerController* playerController = new PlayerController();
 

Я понимаю, что PlayerController может указать на CameraOnlyController позже, вот так

 playerController = new CameraOnlyController();
 

но поскольку это не происходит при инициализации игрока* игрока, почему это предотвращается?
Это компилятор пытается обеспечить безопасность типов, и я предполагаю, что он знает, что в то время PlayerController был назначен новому PlayerController (), но предполагать это неправильно?

Комментарии:

1. playerController имеет тип IGameController* , и IGameController::GetPlayer возвращает GameObject* , нет Player* . Насколько известно компилятору, playerController может указывать на CameraOnlyController какой-либо другой класс, производный от IGameController

2. playerController является ан IGameController* . Насколько известно во время компиляции, это означает playerController->GetPlayer() , что a GameObject* . Да, бывает так , что конкретное значение playerController таково, что GameObject* возвращаемое им значение также является допустимым Player* , но компилятор не может узнать об этом, просто взглянув на определения IGameController , Player , и GameObject .

Ответ №1:

 IGameController* playerController = new PlayerController();
 

playerController это в своем роде IGameController* .

Проверка типа компилятора C больше ничего не помнит playerController . Он забывает тот факт, что он был создан на основе указателя на PlayerController .

 Player* player = playerController->GetPlayer();
 

итак, здесь он берет информацию , которую ему разрешено знать, то playerController есть a IGameController* , и заявляет, что существует несоответствие типов.

Если вы хотите, чтобы компилятор знал больше о типе playerController , вам нужно изменить свой тип playerController . Компилятор C не будет автоматически расширять тип playerController , чтобы он был всем, что он мог знать при определении значения строки кода.

В то же время компилятор C может свободно следовать правилу «как если бы» и изменять тип playerController . Но они могут делать это только в том случае, если они этого не сделали (например, чтобы ускорить ваш код).

Существуют языки программирования, которые допускают более широкое определение типа данной переменной. C не входит в их число.

Вы можете это сделать:

 auto* playerController = new PlayerController();
auto* player = playerController->GetPlayer();
delete playerController;
 

в этом случае будут использоваться точные типы различных переменных, или

 auto* playerController = new PlayerController();
Player* player = playerController->GetPlayer();
delete playerController;
 

что подтверждает, что player это тот тип, который вам нужен.