Eric Smith Eric Smith - 1 month ago 6
C++ Question

Confused with object arrays in C++

So I first learned Java and now I'm trying to switch over to C++. I'm having a little difficulty getting arrays to work correctly.

Right now I am simply trying to create an array of object "Player" and populate it with one. But I get an error.

Player* players = new Player[1];
players[0] = new Player(playerWidth, playerHeight, 20, 1);


The error says:
the operand "=" matches these operands. Operand types are: Player = Player *

I can't understand why this doesn't work?

Answer

What the error is saying is that you are trying to assign a value of the wrong type to the variable. When the error says Player = Player * that means that the variable on the left hand side is a Player and the value on the right hand side is a Player *.

players[0] = new Player(playerWidth, playerHeight, 20, 1);

The problem is similar to if you were to do:

int x;
x = "Hello, World!";

The left and right hand types don't match, and there's no natural conversion, so you get an error.


The first problem is that you're coming from a Java background, and Java uses pointers a lot but hides them from you. C++ doesn't hide them at all. The consequence is that C++ has different syntax for explicitly dealing with pointers. Java got rid of all that and mostly used the regular, non-pointer syntax from C++ for dealing with pointers.

Java:                                  C++:

Player player = new Player();          Player *player = new Player();

Player player2;                        Player *player2 = nullptr;

** no equivalent in java **            Player player3;

player.foo();                          player->foo();

** no equivalent in java **            player3.foo();

** no equivalent in java **            *player;

** no equivalent in java **            &player2;

It's very important to understand the difference between working with pointers and working directly with an object:

Java:                                  C++:

Player a = new Player();               Player *a = new Player();
Player b = a;                          Player *b = a;
b.foo();                               b->foo();

In this code there's only a single object, and you can access it through either a or b and it doesn't make a difference, a and b are both pointers to the same object.

C++:

Player c = Player();
Player d = c;
d.foo();

In this code there are two objects. They are distinct, and doing something to d does not affect c.

If in Java you learned about the distinction between 'primitive' types like int and Object types like String then one way to think about it is that in C++ all objects are primitive. If we look back at your code and use this 'C++ objects are like Java primitives' rule you can maybe see better what's wrong:

Java:
int[] players = new int[1];
players[0] = new int(playerWidth); // huh???

That should make it clear that the right hand side of the assignment should simply be a Player value rather than a dynamic allocation of a new player object. For an int in java this looks like players[0] = 100;. Since Object types in Java are different Java doesn't have a way to write Object values the way you can write int values. But C++ does; players[0] = Player(playerWidth, playerHeight, 20, 1);


The second problem is that arrays in C are weird and C++ inherited that.

Pointers in C and C++ allow 'pointer arithmetic. If you have a pointer to an object you can add to or subtract from it and get a pointer to a different object. Java has nothing similar to this.

int x[2]; // create an array of two ints, the ints are 'adjacent' to one another
// if you take the address for the first one and 'increment' it
// then you'll have a pointer to the second one.

int *i = &x[0]; // i is a pointer to the first element
int *j = &x[1]; // j is a pointer to the second element

// i + 1 equals j
// i equals j - 1

Additionally the array index operator [] works on pointers. x[5] is equivalent to *(x+5). This means that pointers can be used as arrays, and that is idiomatic and expected in C and C++. In fact it's even baked into C++.

In C++ when you use new to dynamically allocate an object, e.g. new Player, you normally get a pointer to the type you specified. In this example you get Player *. But when you dynamically allocate an array, e.g. new Player[5], it's different. Instead of getting back a pointer to an array of five Players, you actually get back a pointer to the first element. This is just like any other Player *:

Player *p   = new Player;    // not an array
Player *arr = new Player[5]; // an array

The only thing that makes this pointer different is that when you do pointer arithmetic on it you get pointers to valid Player objects:

Player *x = p + 1;   // not pointing at a valid Player
Player *y = arr + 3; // pointing at the fourth array element

new and delete are hard to use correctly if you use them without protection. To demonstrate this:

int *x = new int;
foo();
delete x;

This code is error prone and probably wrong. Specifically, if foo() throws an exception then x is leaked.

In C++ whenever you acquire a responsibility, such as when you call new you acquire the responsibility to call delete at a later time, you should remember

R.A.I.I.
Responsibility* Acquisition Is Initialization

* More frequently people say 'resource acquisition is initialization', but resources are only one kind of responsibility. I was persuaded to use the latter term by Jon Kalb in one of his Exception Safe C++ talks.

R.A.I.I. means that whenever you acquire a responsibility, it should look like you're initializing an object; specifically you're initializing a special object who's purpose is to manage that responsibility for you. One example of such an type is std::unique_ptr<int> which will manage pointers to ints allocated with new:

C++:

std::unique_ptr<int> x(new int);
foo();
// no 'delete x;'

To manage your Player array you'd use std::unqiue_ptr like this:

std::unique_ptr<Player[]> players(new Player[1]);
players[0] = Player(playerWidth, playerHeight, 20, 1);

Now the unique_ptr will handle that allocation for you and you don't need to call delete yourself. (N.B. when you allocate an array you should give unique_ptr an array type; std::unique_ptr<Player[]>, and when you allocate anything else you use a non-array type, std::unique_ptr<Player>.)

Of course C++ has an even more specialized R.A.I.I. type for managing arrays, std::vector, and you should prefer that to using std::unique_ptr:

std::vector<Player> players(1);
players[0] = Player(playerWidth, playerHeight, 20, 1);

Or in C++11:

std::vector<Player> players { Player(playerWidth, playerHeight, 20, 1) };
Comments