Ошибка итерации списка подклассов

Я относительно новичок в 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;)

2

Решение

Фактическая причина, по которой вы получаете эту ошибку, заключается в том, что, как вы определили диапазон, для которого вы извлекаете объекты по копии, а не по ссылке:

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
}
}
2

Другие решения

std::list<EngineComponent> это просто так; список объектов компонентов двигателя. Когда вы нажимаете на список, вы делаете копию объекта, который вы нажимаете. Если вы не определили преобразование между подклассом и его базой, оно завершится неудачно. Аналогично, попытка преобразования из базового в подкласс, как вы делаете, также не удастся.

То, что вы, вероятно, хотите, это список указателей на объекты базового класса, т.е. std::list<unique_ptr<EngineComponent>> вероятно, добьется цели. Независимо от того, какой тип указателя вы используете, вам нужно будет уменьшить его до DrawableEngineComponent, прежде чем вы сможете вызвать метод Draw:

для (unique_ptr<EngineComponent>  engCompPtr: engineComponents)
{
DrawableEngineComponent  drawableEngComp = dynamic_cast<DrawableEngineComponent >(* EngCompPtr);
drawableEngComp.Draw ();
}

Я не очень разбираюсь в C #, но я предполагаю, что когда вы работаете с объектами, он на самом деле реализуется умными указателями некоторого описания.

2

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; но даже при этом, было бы целесообразно хранить отрисовываемые из них отдельно (в противном случае вам придется выполнить некоторую приведение и проверку, чтобы убедиться, что она может быть приведена к этому типу).

1
По вопросам рекламы [email protected]