New blog location
New blog location
int
as well as user-defined classes.Grid
class shown in Professional C++. All the following examples are from Professional C++ as well.GamePiece
is the base class and ChessPiece
would derive from it. To copy a GameBoard
, one has to be able to copy GamePiece
s. A pure virtual clone
method could be used:class GamePiece { public: virtual std::unique_ptr<GamePiece> clone() const = 0; }
ChessPiece
implements the clone
method:class ChessPiece : public GamePiece { public: virtual std::unique_ptr<GamePiece> clone() const override; } std::unique_ptr<GamePiece> ChessPiece::clone() const { return std::make_unique<ChessPiece>(*this); //use the copy constructor to copy the instance. }
Gameboard
uses a vector
of vector
:class GameBoard { public: explicit GameBoard(size_t width = kDefaultWidth, size_t height = kDefaultHeight); GameBoard(const GameBoard& src); // copy constructor virtual ~GameBoard() = default; // virtual defaulted destructor GameBoard& operator=(const GameBoard& rhs); // assignment operator GameBoard(GameBoard&& src) = default; GameBoard& operator=(GameBoard&& src) = default; std::unique_ptr<GamePiece>& at(size_t x, size_t y); const std::unique_ptr<GamePiece>& at(size_t x, size_t y) const; size_t getHeight() const { return mHeight; } size_t getWidth() const { return mWidth; } static const size_t kDefaultWidth = 10; static const size_t kDefaultHeight = 10; friend void swap(GameBoard& first, GameBoard& second) noexcept; private: void verifyCoordinate(size_t x, size_t y) const; std::vector<std::vector<std::unique_ptr<GamePiece>>> mCells; size_t mWidth, mHeight; };
GameBoard::GameBoard(size_t width, size_t height) : mWidth(width), mHeight(height) { mCells.resize(mWidth); for (auto& column : mCells) { column.resize(mHeight); } } GameBoard::GameBoard(const GameBoard& src) : GameBoard(src.mWidth, src.mHeight) { // The ctor-initializer of this constructor delegates first to the // non-copy constructor to allocate the proper amount of memory. // The next step is to copy the data. for (size_t i = 0; i < mWidth; i++) { for (size_t j = 0; j < mHeight; j++) { if (src.mCells[i][j]) mCells[i][j] = src.mCells[i][j]->clone(); } } } void GameBoard::verifyCoordinate(size_t x, size_t y) const { if (x >= mWidth || y >= mHeight) { throw std::out_of_range(""); } } void swap(GameBoard& first, GameBoard& second) noexcept { using std::swap; swap(first.mWidth, second.mWidth); swap(first.mHeight, second.mHeight); swap(first.mCells, second.mCells); } GameBoard& GameBoard::operator=(const GameBoard& rhs) { // Check for self-assignment if (this == &rhs) { return *this; } // Copy-and-swap idiom GameBoard temp(rhs); // Do all the work in a temporary instance swap(*this, temp); // Commit the work with only non-throwing operations return *this; } const unique_ptr<GamePiece>& GameBoard::at(size_t x, size_t y) const { verifyCoordinate(x, y); return mCells[x][y]; } unique_ptr<GamePiece>& GameBoard::at(size_t x, size_t y) { return const_cast<unique_ptr<GamePiece>&>(as_const(*this).at(x, y)); }
int
sChessPiece
, one gets back GamePiece
and has to downcast it.Gameboard
is storing in order to properly downcast it.Gameboard
transforms into the generic Grid
class:template <typename T> class Grid { public: explicit Grid(size_t width = kDefaultWidth, size_t height = kDefaultHeight); virtual ~Grid() = default; // Explicitly default a copy constructor and assignment operator. Grid(const Grid& src) = default; Grid<T>& operator=(const Grid& rhs) = default; // Explicitly default a move constructor and assignment operator. Grid(Grid&& src) = default; Grid<T>& operator=(Grid&& rhs) = default; std::optional<T>& at(size_t x, size_t y); const std::optional<T>& at(size_t x, size_t y) const; size_t getHeight() const { return mHeight; } size_t getWidth() const { return mWidth; } static const size_t kDefaultWidth = 10; static const size_t kDefaultHeight = 10; private: void verifyCoordinate(size_t x, size_t y) const; std::vector<std::vector<std::optional<T>>> mCells; size_t mWidth, mHeight; };
Grid
as Grid<T>
. However, outside the class definition, the <T>
cannot be omitted. Templates require one to put the implementation of the methods in the header file itself because the compiler needs to know the complete definition before it can create an instance of the template. template<typename T>
specifier must precede each method definition. Before the scope resolution operator, the name should always be Grid<T>
, and not Grid
. The implementations are as follows:template <typename T> Grid<T>::Grid(size_t width, size_t height) : mWidth(width), mHeight(height) { mCells.resize(mWidth); for (auto& column : mCells) { // Equivalent to: //for (std::vector<std::optional<T>>& column : mCells) { column.resize(mHeight); } } template <typename T> void Grid<T>::verifyCoordinate(size_t x, size_t y) const { if (x >= mWidth || y >= mHeight) { throw std::out_of_range(""); } } template <typename T> const std::optional<T>& Grid<T>::at(size_t x, size_t y) const { verifyCoordinate(x, y); return mCells[x][y]; } template <typename T> std::optional<T>& Grid<T>::at(size_t x, size_t y) { return const_cast<std::optional<T>&>(std::as_const(*this).at(x, y)); }
Grid<int>
or Grid<ChessPiece>
, etc. Pointer types can be stored as well:int* intPtr = new int; Grid<int*> inPtrGrid; intPtrGrid.at(1,1) = intPtr;
vector
:Grid<vector<double>> gridOfDoubleVectors; vector<double> myVec = {1.0,2.0,3.0,4.0}; gridOfDoubleVectors.at(1,2) = myVec;
auto heapGrid = std::make_unique<Grid<double>>(3,3); heapGrid->at(1,2) = 3.14;
Grid<int>
it generates all the code needed for the Grid<int>
. Similarly, if it encounters Grid<ChessPiece>
, it encounters all the necessary code for that. If there are no template instantiations, the class method definitions are not compiled. The compiler always generates code for all virtual methods. For non-virtual methods, it only generates code for the methods that are called.
Comments
Post a Comment