Я относительно новичок в C ++ и, исходя из фона C #, у меня возникли проблемы с этой итерацией списка:
У меня есть один метод, который просматривает список объектов и вызывает метод обновления для каждого, который прекрасно работает. Список имеет тип std::list<EngineComponent>
и называется engineComponents
,
void Game::Update()
{
for (EngineComponent component: this->engineComponents)
{
component.Update();
}
}
У меня также есть подкласс EngineComponent
называется DrawableEngineComponent
,
Проблема появляется, когда я пытаюсь сделать подобную итерацию:
void Game::Draw()
{
for (DrawableEngineComponent component: this->engineComponents)
{
component.Draw();
}
}
Это приводит к ошибке «Не существует подходящего пользовательского преобразования из« EngineComponent »в« DrawableEngineComponent »». Учитывая, что в C # эта реализация была прекрасной, я не совсем уверен, как лучше решить эту проблему в C ++.
Я могу придумать несколько альтернативных способов, которые могли бы / должны работать, но мне интересно, есть ли функциональность в C ++, чтобы сделать это способом, подобным C #, без необходимости вручную определять преобразование.
Определения для этих двух классов следующие:
class EngineComponent
{
public:
EngineComponent(void);
~EngineComponent(void);
virtual void Update(void);
};class DrawableEngineComponent : public EngineComponent
{
public:
DrawableEngineComponent(void);
~DrawableEngineComponent(void);
virtual void Draw(void);
};
И да, я немного копирую платформу XNA;)
Фактическая причина, по которой вы получаете эту ошибку, заключается в том, что, как вы определили диапазон, для которого вы извлекаете объекты по копии, а не по ссылке:
for (EngineComponent component: this->engineComponents)
{
// component is a copy of the object in the list
}
EngineComponent
является суперклассом, и поэтому нет неявного приведения к производному классу. Если вы попытаетесь скопировать DrawableEngineComponent
вне списка EngineComponent
у компилятора нет возможности узнать, действительно ли исходный объект является производным классом.
Стандартные контейнеры не очень хорошо обрабатывают полиморфные объекты. Гораздо лучшим решением является использование std::shared_ptr
хранить указатели на объекты.
std::list<std::shared_ptr<EngineComponent>> myList;
myList.push_back(std::make_shared<DrawableEngineComponent>());
Это обернет DrawableEngineComponent
в общий указатель и сохранить его в списке. Доступ к нему можно получить аналогично исходному методу:
for (auto& component: engineComponents)
{
component->Update();
}
Но на этот раз у вас есть полностью полиморфный объект, который вы можете вызвать. Если объект перегружает Update()
метод в подклассе, то это то, что будет вызываться. Вы также можете использовать приведение, чтобы получить указатель на подкласс, если это то, что вам нужно:
for (auto& component: engineComponents)
{
auto pDrawComponent = dynamic_cast<DrawableEngineComponent*>(component.get());
if (pDrawComponent)
{
// it's drawable
}
}
std::list<EngineComponent>
это просто так; список объектов компонентов двигателя. Когда вы нажимаете на список, вы делаете копию объекта, который вы нажимаете. Если вы не определили преобразование между подклассом и его базой, оно завершится неудачно. Аналогично, попытка преобразования из базового в подкласс, как вы делаете, также не удастся.
То, что вы, вероятно, хотите, это список указателей на объекты базового класса, т.е. std::list<unique_ptr<EngineComponent>>
вероятно, добьется цели. Независимо от того, какой тип указателя вы используете, вам нужно будет уменьшить его до DrawableEngineComponent, прежде чем вы сможете вызвать метод Draw:
для (unique_ptr<EngineComponent> engCompPtr: engineComponents) { DrawableEngineComponent drawableEngComp = dynamic_cast<DrawableEngineComponent >(* EngCompPtr); drawableEngComp.Draw (); }
Я не очень разбираюсь в C #, но я предполагаю, что когда вы работаете с объектами, он на самом деле реализуется умными указателями некоторого описания.
std::list<EngineComponent>
хранит кучу EngineComponents
, Если вы добавите DrawableEngineComponent
к списку вы отсекаете «нарисованную» часть от него:
std::list<EngineComponent> engineComponents;
EngineComponent comp1;
engineComponents.push_back(comp1); // no problem, just copies it into the list
DrawableEngineComponent comp2;
EngineComponent newComp2 = *(EngineComponent*)(&comp2); // the drawable part is now sliced out!
engineComponents.push_back(newComp2); // this adds the sliced version to the list
Скорее всего, вы хотите сохранить список указателей на EngineComponents; но даже при этом, было бы целесообразно хранить отрисовываемые из них отдельно (в противном случае вам придется выполнить некоторую приведение и проверку, чтобы убедиться, что она может быть приведена к этому типу).