tags:

views:

171

answers:

4

I'm just messing around with some C++ at the moment trying to make a simple tic-tac-toe game and I'm running into a bit of a problem. This is my code:

#include <iostream>
using namespace std;

class Square {
public:
    char getState() const;
    void setState(char);
    Square();
    ~Square();
private:
    char * pState;
};

class Board {
public:
    Board();
    ~Board();
    void printBoard() const;
    Square getSquare(short x, short y) const;
private:
    Square board[3][3];
};

int main() {
    Board board;
    board.getSquare(1,2).setState('1');
    board.printBoard();
    return 0;
}

Square::Square() {
    pState = new char;
    *pState = ' ';
}
Square::~Square() {
    delete pState;
}
char Square::getState() const {
    return *pState;
}
void Square::setState(char set) {
    *pState = set;
}

Board::~Board() {

}
Board::Board() {

}
void Board::printBoard() const {
    for (int x = 0; x < 3; x++) {
        cout << "|";
        for (int y = 0; y < 3; y++) {
            cout << board[x][y].getState();
        }
        cout << "|" << endl;
    }
}
Square Board::getSquare(short x, short y) const {
    return board[x][y];
}

Forgive me if there are blatantly obvious problems with it or it's stupidly written, this is my first program in C++ :p However, the problem is that when I try and set the square 1,2 to the char '1', it doesn't print out as a 1, it prints out as some strange character I didn't recognise.

Can anyone tell me why? :)

Thanks in advance.

+2  A: 

Two problems:

1) The getSquare method is const and so returns a const object.

1) The getSquare method is const and so cannot return a non-const reference to a const object.

2) The object returned from getSquare is a copy of the square in the board.

To fix:

1) Remove the const from the getSquare method.

2) alter getSquare to return a reference: Square & Board::getSquare(short x, short y)

James Morris
Although it's good practice to only return const references from const methods, adding the cont qualifier does not affect the return type.
John Gordon
James Morris
That's because you're trying to use a const reference as a non-const one, not because the return type is altered by a method qualifier. Consider a method that returned a reference to something other than *this or a member variable, a static member for example.
John Gordon
I should probably not pretend I know C++ in that case.
James Morris
Hang on... I never said the return type was altered by a method qualifier. I said you cannot initialize a non const reference using a const. Or am I still missing something?
James Morris
Hang on... Yes I did, answer edited.
James Morris
+5  A: 

The Board::getSquare method is returning a copy of the Square object. The pState variable of the copied Square object points to the same character as the original Square object. When the copied Square object is destroyed, it deletes the char object pointed to by the pState variable. This invalidates the char object in the Square object in the Board. When you go to print, you are printing an invalid char object.

As others has stated, the pState variable should probably be a char rather than a char*. This will move you a step forward in resolving your issues. You still need to deal with returning a reference to the Square object rather than a copy of the Square object.

Matthew T. Staebler
+4  A: 
  1. You don't need to use new for creating instances of variables.

  2. Try changing your state variable to char instead of char * (pointer to char).

  3. In general, the char * type is used to indicate a collection (array) of characters terminated by a nul character.

  4. Also, set is a data type in the std namespace. Just change the name to something else, such as new_value.

  5. The getState() method is returning a copy of the state, not a reference to the state. This where Java and C++ differ. Try returning a State&, which is C++ jargon for a reference to a State instance.

  6. Your program is a bit overkill for a Tic-Tac-Toe game; the ancient ones used arrays of char instead of this new fashioned stuff called OO.

Thomas Matthews
Thanks for your answer :DI know that doing an OO version of Tic-Tac-Toe is a little overkill but I'm just trying to get used to the language and its intricacies.
Samwhoo
@Sam, point #4 is an excellent example of why you should avoid `using namespace std;`.
Bill
+3  A: 
Square Board::getSquare(short x, short y) const {
    return board[x][y];
}

In here, you are returing a copy of the Square instance. Since there is no copy constructor, the instance will be copied by memory value. So now there are 2 Square instances which the state points to the same value.

But a Square has a destructor. In the destructor, the state pointer is deleted. But then the remaining copy now owns a dangling pointer.

  1. Square instance A is created.
  2. Char pointer pA is allocated and set to ' ' as A is created.

    +-----+
    | pA -----> ' '
    +-----+
    
  3. Temporary Square instance B is bit-copied from A in getSquare. That means its char pointer pB points to the same location of pA

    +-----+        
    | pA -----> ' '
    +-----+      ^
    +-----+      |
    | pB --------'
    +-----+
    
  4. Square instance B is setState to '1', so the content of pB is changed to '1'

    +-----+        
    | pA -----> '1'
    +-----+      ^
    +-----+      |
    | pB --------'
    +-----+  *pB='1'
    
  5. The temporary Square instance B is destroyed because, well, it's temporary.

    +-----+        
    | pA -----> garbage
    +-----+      ^
    + - - +      |
    : pB --------'
    + - - +  delete pB
    
  6. Now pA points to garbage.

    +-----+        
    | pA -----> garbage
    +-----+
    

You should return a reference to avoid copying,

Square& Board::getSquare(short x, short y) { return board[x][y]; }
//----^ "&" means reference. Similar to pointer, but not rebindable/nullable. 
//      Just think of it as a read-only pointer without needing a "*"

const Square& Board::getSquare(short x, short y) const { return board[x][y]; }
// Both mutable and const versions are needed. (Yes, code duplication.)

and/or provide a copy constructor.

class Square {
public:
  ...
  Square(const Square&);

...
Square::Square(const Square& other) {
  pState = new char;
  *pState = *other.pState;
}

You can just use

class Square {
public:
    char getState() const;  // { return state; }
    void setState(char);    // { state = input; }
private:
    char state;
};

to avoid messing with heap memories. cout << supports printing a char.

And please don't write C++ in Java.

KennyTM
My first language was Java so forgive the transition ^_^
Samwhoo
Brilliant answer, thank you for the explanation! Like I said, still pretty new to all of this.Another question: The getState() method is returning a copy of the char, right? Could I change it to return a pointer to it? And if I did, would that increase efficiency? (I know it's just 60 odd lines of code but I'm interested to know:))Thanks.
Samwhoo
@Samwhoo: You *must* change it to a pointer (or better, a reference) because you want to change the original value, not a copy. See update on how. (Return by pointer (reference) or value won't affect efficiency much due to return-value elision (http://en.wikipedia.org/wiki/Return_value_optimization).)
KennyTM