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

Учитывая базовый класс 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();

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

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

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

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

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

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


person ilzxc    schedule 01.07.2013    source источник
comment
+1 за то, что прочитал эффективный C++   -  person Borgleader    schedule 01.07.2013
comment
Обычно здесь нужно определить draw() как виртуальную функцию в базовом классе. Затем вы могли бы просто вызвать его без приведения, и это было бы правильно. Вы знаете об этом, но хотите знать конкретно об эффектах гипса?   -  person jogojapan    schedule 01.07.2013
comment
Да, я должен был использовать лучшее имя функции, и да, на вопрос в конце вашего комментария.   -  person ilzxc    schedule 01.07.2013
comment
Откуда вы знаете, что [1] содержит анимированный объект? Я имею в виду в реальной версии вашего кода. Часто эта информация может быть перемещена в структуру программы или использована для улучшения упаковки актерского состава или его исключения.   -  person Yakk - Adam Nevraumont    schedule 01.07.2013


Ответы (2)


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

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

Но делать static_cast с полиморфным производным типом — это запах кода.


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

person Potatoswatter    schedule 01.07.2013
comment
Я просто еще раз проголосую за то, чтобы сделать draw виртуальным, чтобы это можно было сделать без кастинга. Это один из классических примеров вещей, для которых виртуальные функции работают идеально. - person Jerry Coffin; 01.07.2013
comment
@jogojapan D'о, я сам запутался. Пользовательский модуль удаления может заставить unique_ptr обрабатывать неполиморфные типы полиморфно. Починю. - person Potatoswatter; 01.07.2013
comment
Большое спасибо. Пойду читать std::shared_ptr - person ilzxc; 01.07.2013
comment
@ilya Я сделал ошибку, std::unique_ptr все будет в порядке без каких-либо передовых методов. Я думал о другом: заставить неполиморфные типы вести себя полиморфно. - person Potatoswatter; 01.07.2013

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

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

Существуют ли структуры данных лучше, чем

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

person SigTerm    schedule 01.07.2013