Я пытаюсь сделать игровую доску из двумерного массива 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++.
Это не минимальный воспроизводимый пример .
Если вы сделаете activate()virtual, вам даже не нужно будет проверять, BreakTile ли это. Просто позвоните activate(). В классе Tileactivate() ничего не будет делать, а в BreakTime он будет делать все, что вы захотите.
Если вы присвоите BreakTile элементу в m_board, он будет нарезан. Вам необходимо использовать указатели (или интеллектуальные указатели) в качестве элементов для хранения любых производных элементов. Это также позволит полиморфизм, как упоминалось выше.
Создайте виртуальный базовый класс class TileItf { virtual ~TileItf() = default; virtual void activate() = 0; }; (это хорошая практика для модульного тестирования и внедрения зависимостей позже). Затем извлеките из этого Tile и добавьте void activate() override к своему Tile. (Также ознакомьтесь с основным правилом C++ 128: виртуальные функции должны иметь значения virtual, override или Final
После этого внутренняя структура данных вашей доски может стать std::array<std::array<std::unique_ptr<TileItf>,9>,9>, и вы сможете создавать плитки и добавлять их с помощью m_board[1][1] = std::make_unique<BreakTile>(); (при условии, что BreakTile также является производным от TileItf). Также вы можете создать класс «EmptyTile», который ничего не делает при вызове активации, и инициализировать вашу доску с его помощью (шаблон нулевой стратегии, таким образом вам не понадобятся if операторы, чтобы проверить, пусто ли поле)
Обратите внимание: то, о чем говорит @PepijnKramer, чаще называют «абстрактным базовым классом», а не «виртуальным базовым классом». Если вы хотите узнать о них больше, это, вероятно, даст лучшие результаты.
@PepijnKramer: На самом деле это не поправка - я не думаю, что «виртуальный базовый класс» неправильный. Но я почти уверен, что видел «абстрактный базовый класс» чаще.





Создайте виртуальный базовый класс 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();
}
Используйте
virtual. Кроме того, полиморфизм работает только через указатели/ссылки.