Существуют ли риски производительности для использования статического приведения для работы с вектором смешанных (базовых и производных) объектов? (ака "это глупая идея?")

учитывая базовый класс gameObject и производный класс animatedGameObject, Я подумал, что может быть хорошо хранить все их экземпляры в std::vector. Если вектор GameObjects объявляется базовым типом gameObject*, экземпляры производных объектов требуют приведения.

пример:

vector<gameObject*> GameObjects;

gameObject A* = new gameObject( ...init... ); 
animatedGameObject B* = new animatedGameObject( ...init... );

GameObjects.push_back(A);
GameObjects.push_back(B);

// to access the animatedGameObject functions:
static_cast<animatedGameObject*>(GameObjects[1])->functionUniqueToAnimated();

боясь, как обычно, я обратился к Скотту Мейерсу (эффективный C++, 3-е издание), который пишет на эту тему:

многие программисты считают, что слепки ничего не делают но скажите компиляторам, чтобы они рассматривали один тип как другой, но это ошибочно. Преобразования типов любого рода (явных или неявных приведений через компиляторами) часто приводят к коду, который выполняется во время выполнения.

Я прочитал его пункт 27: минимизировать кастинг дважды, но, учитывая мою неопытность в этом, я борюсь с моей неспособностью ответить на простой вопрос " РАЗВЕ ЭТО ГЛУПО?"

Я должен упомянуть, что есть несколько причин почему это is глупая вещь, которая не имеет ничего общего с вызовом static_cast. Вопросы в порядке важности:--10-->

  1. я не вижу некоторых возможных рисков с использованием static_cast в моем примере выше?
  2. есть ли лучшие структуры данных, чем std::vector для таких подходов? (только если есть что-то очевидное, я не прошу вас делать мои исследования для меня.)

это мой первый раз задаю здесь вопрос , так что извиняюсь заранее, когда это необходимо.

2 ответов


static_cast не является правильным инструментом для задания, если вы не знаете, что указатель идет на animatedGameObject, а не gameObject. Какую структуру данных вы используете для хранения этой информации?

определение типа производного объекта после базового указателя является задачей динамической отправки или dynamic_cast. В вашем примере, вызов GameObjects[1]->draw() должен работать без броска, потому что draw должна быть виртуальной функцией. В противном случае вы можете использовать dynamic_cast< animatedGameObject & >( * GameObjects[1] ) утверждать, что объект является animatedGameObject, и бросить std::bad_cast исключение, если это не так. (Для этого все равно потребуется на class gameObject , как правило, его деструктор.)

но делать static_cast для полиморфного производного типа является запах кода.


также вы спрашиваете,std::vector хорошая структура данных для этого примера. Это, но не вектор" голых " указателей. C++11 теперь предоставляет классы управления памятью" smart pointer", которые выполняют new и delete для вас, рендеринг фактических операторов почти устарел. Посмотрите в std::unique_ptr для данного случая.


  1. Если gameObjects[1] не является animatedGameObject, приложение (скорее всего) умрет ужасно.
  2. Если draw () - виртуальный метод, присутствующий в gameObject, преобразование не требуется.

в общем случае приведение базового класса к производному классу небезопасно. В этих ситуациях имеет смысл использовать dynamic_cast. Dynamic_cast возвращает NULL, если преобразование не может быть выполнено.

есть ли лучшие структуры данных чем

Ну, если ваши gameObjects не удаляются автоматически в другом месте, может иметь смысл использовать что-то вроде std::vector<std::shared_ptr<gameObject> >. Однако стандартные общие указатели могут вводить скрытые накладные расходы (дополнительное новое/удаление, в худшем случае они могут даже ввести многопоточную блокировку мьютекса, если они предназначены для потокобезопасности), поэтому вы должны убедиться, что эти накладные расходы совместимы с вашими целями.