Есть ли способ вызвать метод с указателем базового класса, который использует данные в производном классе?

#c #pointers #vector #base-class

Вопрос:

(извините за мой плохой английский)

У меня есть базовый класс с вектором указателей на рисоваемые объекты в нем и методом draw (), который использует данные из этого вектора.

 class GameObject
{
protected:
    std::vector<Drawable*> drawable;
...

void GameObject::draw() { for (const autoamp; object : drawable) window.draw(*object); }
 

В производных классах я хочу иметь возможность добавлять некоторые рисоваемые объекты

 class Player : public GameObject
{
protected:
    RectangleShape shape;
...

Player::Player(float x, float y, float z)
{
    shape.setPosition [...]
    drawable.push_back(amp;shape);
...
 

и нарисуйте их, используя метод указателя базового класса

 std::vector<GameObject*> gameObjects;
...
for (autoamp; gameObject : gameObjects) gameObject->draw();
 

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

upd:

 Level::Level()
{
    player = Player(500.f, 500.f); //Player player; in header file
    gameObjects.push_back(amp;player);
}

void Level::display()
{
    for (auto gameObject : gameObjects) gameObject->draw();
}
 

Комментарии:

1. » Программа выходит из строя (я думаю, потому что базовый класс ничего не знает о векторных данных в производном классе) » — это не проблема. Код выглядит нормально для меня, так что, скорее всего, он выйдет из строя в другом месте кода, который вы не показали. Например, если GameObject* указатели в gameObjects указывают на недопустимые объекты. Вы не показали, как gameObjects заселяется.

2. То, что вы показали до сих пор, будет работать нормально. Однако, если вы когда-либо сделаете копию Player объекта, Player::GameObject::drawable вектор в новой копии по-прежнему будет содержать адрес Player::shape в старой копии, что приведет к проблемам. Поэтому обязательно отключите копирование и перемещение объекта при этом.

3. @BenVoigt или просто внедрите конструкторы копирования/перемещения Player , чтобы добавить новое shape в drawable вектор, а не старое shape .

4. @RemyLebeau: Это не кажется «простым», если в вектор могут быть добавлены элементы drawable на нескольких уровнях иерархии, некоторые из которых, возможно, потребуется глубоко скопировать, некоторые перевести на новый адрес подобъекта, а некоторые оставить в покое (возможно, они относятся к глобальным). Да, можно написать конструктор копирования и перемещения, если эти операции необходимы. Скорее всего, это не так, и они вызываются случайно, что покажет удаление конструктора.

5. @RemyLebeau извините, обновлено

Ответ №1:

Проблема в коде, добавленном вашей правкой-похоже, мой хрустальный шар сегодня работает.

Вы создаете временную Player переменную и перемещаете ее в player переменную-член. Это заканчивается вектором , содержащим адрес shape внутри временного Player , который немедленно уничтожается, оставляя висящий указатель.

Используйте список инициализаторов ctor, чтобы избежать перемещения:

 Level::Level()
    : player(500.f, 500.f /* where did Z go? */)
{
    gameObjects.push_back(amp;player);
}
 

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

 class Player
{
    // ...

    Playeramp; operator=(const Playeramp;) = delete;
    Playeramp; operator=(Playeramp;amp;) = delete;
};