Как вызвать функцию из дочернего класса в массиве объектов родительского класса в C++

Я пытаюсь сделать игровую доску из двумерного массива Tile объектов, класса, который я создал. Это выглядит примерно так:

class Board {
private:
    Tile m_board[9][9]
public:
    Board();
    //Constructors, functions, etc
};

class Tile {
private:
    char m_type;
    //other class members, etc
public:
    Tile();
    char getType();
    void activate(); //This does nothing.
};

class BreakTile: public Tile {
public:
    BreakTile();
    void activate(); //This does something.

Я хочу, чтобы я мог подойти к любому Tile на доске, проверить, является ли это BreakTile, и если да, то активировать его. У меня есть функция в классе Board, которая выглядит примерно так:

void Board::activate(int y, int x)
{
    if (m_board[y][x].getType() == 'B')
    {
        m_board[y][x].activate();
    }
}

И это может активировать BreakTiles. Однако все, что он делает, это вызывает функцию activate() из класса Tile - предположительно потому, что это массив Tile - несмотря на то, что в конструкторе Board я определяю некоторые плитки как BreakTile, поэтому я знаю, какие плитки ищу. здесь BreakTiles. Я просто не знаю, как заставить компьютер это распознать.

Я пытался посмотреть, смогу ли я каким-то образом преобразовать Tile в BreakTile, но понятия не имею, как мне вообще начать это делать. Я новичок в C++.

Используйте virtual. Кроме того, полиморфизм работает только через указатели/ссылки.

user12002570 30.07.2024 17:33

Это не минимальный воспроизводимый пример .

user12002570 30.07.2024 17:34

Если вы сделаете activate()virtual, вам даже не нужно будет проверять, BreakTile ли это. Просто позвоните activate(). В классе Tileactivate() ничего не будет делать, а в BreakTime он будет делать все, что вы захотите.

Ted Lyngmo 30.07.2024 17:35

Если вы присвоите BreakTile элементу в m_board, он будет нарезан. Вам необходимо использовать указатели (или интеллектуальные указатели) в качестве элементов для хранения любых производных элементов. Это также позволит полиморфизм, как упоминалось выше.

wohlstad 30.07.2024 17:38

Создайте виртуальный базовый класс class TileItf { virtual ~TileItf() = default; virtual void activate() = 0; }; (это хорошая практика для модульного тестирования и внедрения зависимостей позже). Затем извлеките из этого Tile и добавьте void activate() override к своему Tile. (Также ознакомьтесь с основным правилом C++ 128: виртуальные функции должны иметь значения virtual, override или Final

Pepijn Kramer 30.07.2024 17:45

После этого внутренняя структура данных вашей доски может стать std::array<std::array<std::unique_ptr<TileItf>,9>,9>, и вы сможете создавать плитки и добавлять их с помощью m_board[1][1] = std::make_unique<BreakTile>(); (при условии, что BreakTile также является производным от TileItf). Также вы можете создать класс «EmptyTile», который ничего не делает при вызове активации, и инициализировать вашу доску с его помощью (шаблон нулевой стратегии, таким образом вам не понадобятся if операторы, чтобы проверить, пусто ли поле)

Pepijn Kramer 30.07.2024 17:46

Обратите внимание: то, о чем говорит @PepijnKramer, чаще называют «абстрактным базовым классом», а не «виртуальным базовым классом». Если вы хотите узнать о них больше, это, вероятно, даст лучшие результаты.

Jerry Coffin 30.07.2024 17:59

@PepijnKramer: На самом деле это не поправка - я не думаю, что «виртуальный базовый класс» неправильный. Но я почти уверен, что видел «абстрактный базовый класс» чаще.

Jerry Coffin 30.07.2024 23:44
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
8
55
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Создайте виртуальный базовый класс TileItf (это хорошая практика для модульного тестирования и внедрения зависимостей позже). Затем извлеките Tile из этого и добавьте к нему виртуальный activate() (также ознакомьтесь с C++ Core Guideline 128, виртуальные функции должны иметь virtual, override или Final).

После этого внутренняя структура данных вашей доски может стать двумерным массивом std::unique_ptr<TileItf> объектов, и вы можете создавать плитки и добавлять их, используя:

m_board[1][1] = std::make_unique<BreakTile>();

(при условии, что BreakTile также является производным от TileItf).

Кроме того, вы можете создать класс EmptyTile, который ничего не делает при вызове activate(), и инициализировать свою доску с его помощью (шаблон нулевой стратегии, таким образом вам не понадобятся операторы if для проверки, пусто ли поле).

Вот пример:

#include <array>
#include <memory>
#include <iostream>

// First define an abstract base class (or "interface")
class TileItf
{
public:
    virtual ~TileItf() = default;
    virtual void activate() = 0;
};

// then create overloads for any kind of tile you can have
class EmptyTile : public TileItf
{
    void activate() override { std::cout << "."; }
};

class OTile : public TileItf
{
    void activate() override { std::cout << "O"; }
};

class XTile : public TileItf
{
    void activate() override { std::cout << "X"; }
};

// The board now will contain a 2D array of interfaces to tiles.
// in C++ we don't use new/delete by default anymore, but use containers or smart pointers
class Board
{
public:
    Board()
    {
        // intitialize the board with empty tiles
        for (auto& row : m_tiles)
        {
            for (auto& tile : row)
            {
                tile = std::make_unique<EmptyTile>();
            }
        }

        // this next line looks simple but it does a few things
        // it will destroy the tile at the current position and assign a new tile to it. 
        m_tiles[0][1] = std::make_unique<OTile>();
        m_tiles[1][1] = std::make_unique<XTile>();
    }

    void activate()
    {
        // range based for loops (cannot go out of scope)
        for (const auto& row : m_tiles)
        {
            for (const auto& tile : row)
            {
                tile->activate(); // <== here the derived activate function will be called depending on the concrete tile type 
            }
            std::cout << "\n";
        }
    }

private:
    // Because tiles are polymorphic we need pointers to them (or you get the object slicing that was talked about before)
    std::array<std::array<std::unique_ptr<TileItf>, 3>, 3> m_tiles;
};

int main()
{
    Board board;
    board.activate(); 
}

Другие вопросы по теме