#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()
, что aGameObject*
. Да, бывает так , что конкретное значение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
это тот тип, который вам нужен.