Odd bug which doesn't seem to have a logical explanation

The following is my code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
#include <iostream>

using namespace std;

class Matris {
	private:
		int* m_vec;
	public:
		Matris() {
			m_vec = new int[10];
			for(int i=0; i < 10; i++) {
				m_vec[i] = 0;
			}
		}
};

class ChessPiece {
    public:
    	int x,y;
        bool isWhite;
        virtual char utf8mb4Representation() = 0;
};

class Rook : public ChessPiece {  
	public:
		Rook(int x, int y, bool isWhite) {
    		x = x;
    		y = y;
    		isWhite = isWhite;
    	}
    	virtual char utf8mb4Representation() {
        	if(isWhite) {
        		return 'R';
        	}
        	return 'r';
        }
};

int main(void) {
	Matris s;
	ChessPiece* piece = new Rook(0, 0, false);
	cout<<piece->isWhite; //Prints 1 (wrong)
	Rook piece2(0, 0, false);
	cout<<piece2.isWhite; //Prints 0 (correct)
    
	return 0;
}


Dynamically allocating a ChessPiece object (subclass Rook) and printing out the isWhite variable wrongly prints 1 (true) while allocating such an object on the stack instead and printing its isWhite variable out correctly prints 0 (false). Both objects are allocated with the same constructor arguments. I have found four ways to solve the problem: modifying the Rook constructor's parameter names to be different from the ChessPiece member variables, removing the dynamic allocation in the Matris constructor, and removing the virtual utf8mb4Representation() function.

What exactly is going on here?
Last edited on
isWhite = isWhite;
Does this make sense to you? You're setting a variable to be... the same value that it already has?

Also, are you sure you know which variable isWhite actually is here?
I've read that compilers are smart enough to realize that when you write such a statement inside a constructor, it should assign the class member's value since it makes no sense to update the original variable to have the same value as it had before the update. I could be wrong, though. The troubling factor here may be that the member variables aren't declared explicitly inside Rook but are rather inherited from ChessPiece. The problem even goes away when I remove the x and y ChessPiece members.
Last edited on
You may be right since if I add ChessPiece:: before the left-hand side of isWhite = isWhite, the problem goes away as well.
You definitely have masking in effect. Perfectly logical.
1
2
3
4
5
6
7
Rook::Rook(int x, int y, bool isWhite)
{
  // within this scope (of function body) the argument names mask members
  x = x;
  y = y;
  isWhite = isWhite;
}

The reason that you get different (undefined) values is due to your platform's stack and free store having whatever they have.

One could use this:
1
2
3
4
5
6
Rook::Rook(int x, int y, bool isWhite)
{
  this->x = x;
  this->y = y;
  this->isWhite = isWhite;
}

One could use explicit scope:
1
2
3
4
5
Rook(int x, int y, bool isWhite) {
  ChessPiece::x = x;
  ChessPiece::y = y;
  ChessPiece::isWhite = isWhite;
}


However, the most logical solution is to delegate the construction of a ChessPiece to the constructor of ChessPiece:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class ChessPiece {
public:
  ChessPiece( int x, int y, bool isWhite )
  : x( x ), y( y ), isWhite( isWhite ) // unambiguous 
  {
  }
};

class Rook : public ChessPiece {  
public:
  Rook( int x, int y, bool isWhite )
  : ChessPiece( x, y, isWhite ) 
  {
  }
};


Unambiguous?
1
2
3
4
5
6
class Foo {
public:
  Foo( /*args*/ )
  : bar( gaz )
  { }
};

Q: Why could gaz have same identifier as bar in this?
A: The member initializer list is not the same scope as the the body of the function. The "bar" that is initialized in it has to resolve to data member of the class. However, the initializer "gaz" is first looked from the arguments. Hence no masking.

For human reader it is better to keep the argument name and the member name slightly different.
I've read that compilers are smart enough to realize that when you write such a statement inside a constructor, it should assign the class member's value since it makes no sense to update the original variable to have the same value as it had before the update. I could be wrong, though.

You are wrong. What you probably read - and misunderstood - is that in an initialisation list, you can use the same name for the data member and the constructor argument. This is because, in the initialisation list, the syntax makes it unambiguous when the data member is being referenced, and when the argument is being referenced.
I've read that compilers are smart enough to realize that...


How could the compiler possibly know if you meant

this->isWhite = isWhite

or

isWhite = this->isWhite
Topic archived. No new replies allowed.