0

I want to have classes named chessman and pawn, for example. I can't make chessman an abstract class because I need to create an 2D array of it. pawn class inherits chessman and I need to use move function of pawn when I write this:

chessman *cm = new pawn("a3", 'w');
cm->move(pos);

This code uses chessman::move. What can I do to make it use the one from pawn class? My question is very similar to this question, but my function doesn't take pawn as an argument. In Java, it's easy since you can create arrays of abstract classes, but in C++ it is just confusing.

EDIT:

Here is definition of chessman class (function already is virtual as you can see):

class chessman
{
public:
    int i;
    int j;
    string name;
    char color;
    virtual bool move(string final_pos);
};

And pawn:

class pawn : public chessman
{
public:
    pawn(string pos, char color);
    bool move(string final_pos);
};
Community
  • 1
  • 1
omerfarukdogan
  • 839
  • 9
  • 26
  • 3
    This looks like a case for Polymorphism: http://www.cplusplus.com/doc/tutorial/polymorphism/ – NathanOliver Feb 18 '15 at 16:52
  • read about virtual functions. – sanjayk79 Feb 18 '15 at 16:53
  • As I said, I can't make the function virtual because I need to create an instance of the class. I'm reading about Polymorphism, thanks for the reply. – omerfarukdogan Feb 18 '15 at 16:56
  • You can and probably should make them virtual. What you mean is you can't make them pure virtual. – Luchian Grigore Feb 18 '15 at 16:57
  • 2
    @farukdgn You actually can make functions `virtual` without the need for having them abstract (_pure virtual_) in the base. – πάντα ῥεῖ Feb 18 '15 at 16:58
  • Ah, yes, I was talking about making pure virtual. But I already tried using virtual function, but it didn't help – omerfarukdogan Feb 18 '15 at 16:59
  • 1
    @farukdgn Not only can you make the function virtual, but you can probably make it pure virtual. Your difficulty lies in trying to instantiate an array of the base class which in C++ is not at all what you need. It's quite different from Java where all classes are passed around as references. – Mark B Feb 18 '15 at 16:59
  • I make the function virtual, and I get LNK2019 errors if I don't define it in chessman class, and if I define it in chessman and use the code below, it still uses the one in the chessman class. – omerfarukdogan Feb 18 '15 at 17:01
  • Personally I wouldn't use polymorphism for this. I would just create an enum field depending what piece it is. – Neil Kirk Feb 18 '15 at 18:30
  • @NeilKirk: That means `move()` has to look at the enum and perform any number of different validations based on the enum value. That also puts the burden on the caller to pass the correct enum to `move()`. Polymorphism makes that more isolated and specialized. What you describe is how it should be done in C, not in C++. – Remy Lebeau Feb 18 '15 at 19:20
  • @RemyLebeau This is Chess, right? There are not that many possibilities. Why is the burden on the caller? The enum field is still a member of the class. Is it worth the dynamic memory allocations just to use the fancy feature of virtual functions? Right tool for the right job. – Neil Kirk Feb 18 '15 at 20:26
  • @NeilKirk: # of possible moves - pawn: 4, rook: 5, knight: 8, bishop: 4, queen: 8, king: 9. That's 38 possible moves. Obviously, `move()` would have to filter by piece type, then check that type's specific possibilities. Which is the same thing that the polymorphic solution would also do. – Remy Lebeau Feb 18 '15 at 23:04
  • @RemyLebeau I don't understand why 6 virtual functions is super and 1 function with a switch statement and 6 cases is so bad. You can even get some redundancy as, for example, the queen moves as a bishop and a rook. I've done Chess AI before. – Neil Kirk Feb 19 '15 at 00:01

2 Answers2

3

I can't make chessman an abstract class because I need to create an 2D array of it.

Yes, you can. You don't create an array of chessman objects, you create an array of pointers to chessman objects. Then chessman can be abstract (as it should be, since you should not be creating instances of chessman directly to begin with).

pawn class inherits chessman and I need to use move function of pawn when I write this

Polymorphism handles that for you. But in order to use an array of polymorphic objects, the array needs to hold pointers/references to objects that are stored elsewhere in memory, not hold the actual objects themselves.

In Java, it's easy since you can create arrays of abstract classes, but in C++ it is just confusing.

You do the exact same thing in C++. In Java, objects are reference types, so they are always referenced by pointer (the Java language simply hides that detail from you).

Try something like this:

class chessman
{
private:
    virtual bool isValidMove(string final_pos) = 0;

public:
    int i;
    int j;
    string name;
    char color;
    chessman(string aname, char acolor);
    bool move(string final_pos);
};

chessman::chessman(string aname, char acolor)
    : name(aname), color(acolor)
{
}

bool chessman::move(string final_pos)
{
    // validate that final_pos is a valid position on the board...

    // validate that final_pos is a valid position for the piece being moved...
    if (!isValidMove(final_pos))
        return false;

    // move to the position...

    return true;
}

class pawn : public chessman
{
private:
    virtual bool isValidMove(string final_pos);

public:
    pawn(string pos, char color);
};

pawn::pawn(string pos, char color)
    : chessman("pawn", color)
{
    //...
}

bool pawn::isValidMove(string final_pos)
{
    // validate that final_pos is a valid position for this pawn to move to...
    return ...;
}

class rook : public chessman
{
private:
    virtual bool isValidMove(string final_pos);

public:
    rook(string pos, char color);
};

rook::rook(string pos, char color)
    : chessman("rook", color)
{
    //...
}

bool rook::isValidMove(string final_pos)
{
    // validate that final_pos is a valid position for this rook to move to...
    return ...;
}

class knight : public chessman
{
private:
    virtual bool isValidMove(string final_pos);

public:
    knight(string pos, char color);
};

knight::knight(string pos, char color)
    : chessman("knight", color)
{
    //...
}

bool knight::isValidMove(string final_pos)
{
    // validate that final_pos is a valid position for this knight to move to...
    return ...;
}

class bishop : public chessman
{
private:
    virtual bool isValidMove(string final_pos);

public:
    bishop(string pos, char color);
};

bishop::bishop(string pos, char color)
    : chessman("bishop", color)
{
    //...
}

bool bishop::isValidMove(string final_pos)
{
    // validate that final_pos is a valid position for this bishop to move to...
    return ...;
}

class queen : public chessman
{
private:
    virtual bool isValidMove(string final_pos);

public:
    queen(string pos, char color);
};

queen::queen(string pos, char color)
    : chessman("queen", color)
{
    //...
}

bool queen::isValidMove(string final_pos)
{
    // validate that final_pos is a valid position for this queen to move to...
    return ...;
}

class king : public chessman
{
private:
    virtual bool isValidMove(string final_pos);

public:
    king(string pos, char color);
};

king::king(string pos, char color)
    : chessman("king", color)
{
    //...
}

bool king::isValidMove(string final_pos)
{
    // validate that final_pos is a valid position for this king to move to...
    return ...;
}

Then you can do somthing like this:

chessman* white_pieces[16];
chessman* black_pieces[16];

for (int i = 0; i < 8; ++i)
{
    white_pieces[i] = new pawn(...);
    black_pieces[i] = new pawn(...);
}

for (int i = 8; i < 10; ++i)
{
    white_pieces[i] = new rook(...);
    black_pieces[i] = new rook(...);
}

for (int i = 10; i < 12; ++i)
{
    white_pieces[i] = new knight(...);
    black_pieces[i] = new knight(...);
}

for (int i = 12; i < 14; ++i)
{
    white_pieces[i] = new bishop(...);
    black_pieces[i] = new bishop(...);
}

white_pieces[14] = new queen(...);
black_pieces[14] = new queen(...);

white_pieces[15] = new king(...);
black_pieces[15] = new king(...);

And move them around as needed:

white_pieces[index]->move(pos);
...
black_pieces[index]->move(pos);

And of course, don't forget to cleanup when you are done:

for (int i = 0; i < 16; ++i)
{
    delete white_pieces[i];
    delete black_pieces[i];
}

To make the cleanup automatic, you can use an array of std::auto_ptr<chessman> objects. Or, in C++11 and later, a std::vector/std::array of std::unique_ptr<chessman> objects.

Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
  • I have problems about creating 2D array of pointers. I define it with `chessman *board[8][8];` and try to access with `*(board[i][j]).color`. But IntelliSense tells me that `expression must have class type`. – omerfarukdogan Feb 18 '15 at 18:54
  • Use `(*(board[i][j])).color` or `board[i][j]->color` instead. – Remy Lebeau Feb 18 '15 at 19:05
  • Oh sorry, my bad. But I expected *(board[i][j]).color to work. Does it not seem okay? – omerfarukdogan Feb 18 '15 at 19:09
  • 1
    No, it is not OK, because of [Operator Precedence](http://en.cppreference.com/w/cpp/language/operator_precedence). The `.` operator has a higher priority than the `*` operator, so `*(board[i][j]).color` is essentially the same as `*((board[i][j]).color)` - trying to dereference the `color` value instead of the `chessman` pointer, hence the error and why the extra explicit parenthesis are needed to tell the compiler what you want the `*` operator applied to. Using `->` instead makes this cleaner. When using pointers, you really should be using `ptr->member` and not `(*ptr).member`. – Remy Lebeau Feb 18 '15 at 19:13
  • Thanks! I already use ->, but since I'm new to C++, I usually mix things with Java. – omerfarukdogan Feb 18 '15 at 19:15
1

In C++ you would create a vector (or possibly array in C++11) of some sort of pointer (the pointer type would be dictated by the object ownership) to a chessman, and have chessman be abstract. It's just a slightly different way of thinking about things.

(Edited as the comments made a great point): To make it select the proper move function, utilize the virtual mechanism in the base class. I would suggest to separate interface from implementation by not having public virtual functions. Instead, use a public non-virtual interface and a private or protected virtual implementation. You would use a private implementation which each child totally replaces the functionality while you would use protected when the child class needs to also invoke the parent functionality.

Mark B
  • 95,107
  • 10
  • 109
  • 188
  • 1
    Regarding your last sentence, I disagree. In fact, I think this `move` is a perfect candidate for a non-virtual public function, because there are basic `pos` checks independent of any concrete chessman, namely checking if the position is within the board. Pseudo code: `void move(pos p) { if (p not within board) then error handling else doMove(p); }`. – Christian Hackl Feb 18 '15 at 17:12
  • `move()` should be virtual because different types of chess pieces have different rules regarding their valid moves. `chessman::move()` could do the basic validation of making sure the position is within the board, but then each piece should override `move()` to make sure the position is also a valid move for that particular type of piece. – Remy Lebeau Feb 18 '15 at 18:02
  • @RemyLebeau: I am talking about making `move` non-virtual and have it delegate to a private virtual function. Requiring derived classes to call parent functions is error-prone. In C++, the guideline is to make virtual functions private and public functions non-virtual (except of the destructor, of course). – Christian Hackl Feb 18 '15 at 18:08
  • P.S.: This old article by Herb Sutter still sums it up nicely: http://www.gotw.ca/publications/mill18.htm – Christian Hackl Feb 18 '15 at 18:09
  • @ChristianHackl: virtual methods that are `private` cannot be overridden in descendants, they need to be `protected` or `public` instead. But I understand what you are saying. – Remy Lebeau Feb 18 '15 at 18:16
  • 2
    @RemyLebeau: This is wrong. In C++, you *can* override private virtual methods. And you *should* do so, too, as I said. Please refer to the article, or just try it in any compiler. – Christian Hackl Feb 18 '15 at 18:27
  • @ChristianHackl: In my 16 years of being a C++ programmer, I have *never once* heard that a `private` virtual method can be overridden in a descendant class. `private` data members are not accessible to descendants, and `private` base methods cannot be called directly by descendants. But a `private` virtual method can be overridden by a descendant? That is new to me. I tried it, and it works! – Remy Lebeau Feb 18 '15 at 18:36
  • @RemyLebeau: Well, I remember it sounded wrong to me, too, when I heard it for the first time. But it quickly started to feel normal, and nowadays I sorely miss it in languages like Java (which do not allow it). I consider this distinction a great feature of C++, as it better separates client concerns from implementer concerns. – Christian Hackl Feb 18 '15 at 18:41