1

Suppose the following code:

struct Piece_t {
    virtual ~Piece_t() = default;
    std::string type;
};

struct Empty_t : Piece_t {
    Empty_t() { };
    bool run() { return false; };
};

struct Pawn_t : Piece_t {
    Pawn_t() {
        type = "Pawn";
    }
    bool move() {
        return false;
    }
};

struct Board_t {
    using Cols = std::vector<Piece_t>;
    using Board = std::vector<Cols>;
    Board_t(size_t N = 8) : b{ Board(N, Cols(N, Empty_t())) } {
        for (size_t i = 0; i < N; i++) {
            b[1][i] = Pawn_t();
            b[N - 2][i] = Pawn_t();
        }
        //more init's here...
    }
    Board b;
};

Here I have a Piece_t, and Im creating a Board_t, which has a std::vector<std::vector<Piece_t>>. Fair enough, however, now suppose that I want to add a pure virtual function to Piece_t, like move:

struct Piece_t {
    virtual bool move() = 0;
    virtual ~Piece_t() = default;
    std::string type;
};

Doing this, however, is going to give me compilation errors, because I can not instantiate a std::vector<Piece_t> like I do in Board. This seems strange to me, because I'm actually doing an init with Empty_t, and not with Piece_t. So here are my questions:

  1. Why is not letting me to compile the code with this modification? Suppose that I' have already implemented move in the other two structs.
  2. What should be the correct pattern to create the desired behaviour? (Being creating a base class with a virtual pure funct. and later using that definition to getting advantage of polymorphism.
Jarod42
  • 203,559
  • 14
  • 181
  • 302
Norhther
  • 545
  • 3
  • 15
  • 35

2 Answers2

2

Why is not letting me to compile the code with this modification?

You are experiencing object slicing. std::vector<Piece_t> stores only objects of type Piece_t. Imagine a contiguous array of Piece_t classes back-to-back. There is no room to store anything more in between.

Since Pawn_t is a Piece_t, it's sliced back down to Piece_t and that part gets stored. Unfortunately an instance of Piece_t can't exist since it's an abstract class, hence the compile error.

What should be the correct pattern to create the desired behaviour?

Store pointers (or references) to objects instead.

For example, using std:vector<std::unique_ptr<Piece_t>>.

struct Board_t {
    using Row = std::vector<std::unique_ptr<Piece_t>>;
    using Board = std::vector<Row>;
    Board_t(size_t N = 8) : b(N) {
        for (auto& row : b)
            row.resize(N);
        for (size_t i = 0; i < N; i++) {
            b[1][i] = std::make_unique<Pawn_t>();
            b[N - 2][i] = std::make_unique<Pawn_t>();
        }
        //more init's here...
    }
    Board b;
};
rustyx
  • 80,671
  • 25
  • 200
  • 267
  • Thanks mate, also I see that `C.67` provides a way to ensure that `slicing` is not going to happen :D – Norhther Mar 30 '21 at 19:41
-1

c++ runtime polymorphism is usually obtained instantiating the derived classes on the heap and accessing them via base class pointers/references, for example:

class Piece_t {};
class Empty_t : Piece_t {};
class Pawn_t : Piece_t {};
std::vector<std::unique_ptr<Piece_t>> vec;

std::unique_ptr<Piece_t> e1 = std::make_unique<Empty_t>();
vec.push_back(std::move(e1));
vec.push_back(std::make_unique<Pawn_t>());

vec[0]->some_virtual_method();

Note that this is not the only polymorphic mechanism available in c++. You may want to read a good c++ book while experimenting.

MatG
  • 574
  • 2
  • 7
  • 19