Confused with object arrays in C++
Your types don't match. And it's no wonder, you're trying to store a Player*
into an already-allocated Player
!
Player* players = new Player[1];
This creates an array of length 1, containing an instantiated Player
, and stores the whole thing into a Player*
. The type of players[0]
is going to be Player
.
players[0] = new Player(...)
This attempts to create a new Player*
and store it in the array. But the array contains Player
objects. You should just say
players[0] = Player(...)
Alternatively, and I'm going to guess this is more appropriate for you, you should stop using new
entirely, and use a std::vector
.
std::vector<Player> players;
players.push_back(Player(playerWidth, playerHeight, 20, 1));
// or players.emplace_back(playerWidth, playerHeight, 20, 1);
Not only is this much easier to use, but you also don't have to remember to delete
it later. When the std::vector
goes out of scope, it will automatically destruct. Also, unlike your array, std::vector
can contain any number of objects, so you can add new players or delete existing players at will.
There are other data structures as well that may possibly be more suited for you, depending on your exact use, but std::vector
is a good starting point.
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 int
s 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) };
The reason is, type of your variable
players[0]
is Player (object). However, operator "new" (new Player) returns a pointer (Player*)
If you want to have only one object, correct way to do it will be:
Player* player = new Player(playerWidth, playerHeight, 20, 1);
And don't forget in C++ you need to clean the mess after yourself - somewhere in the end call
delete player;
for every object you've created. C++ does not have Garbage Collector - meaning all manually created (by "new") objects stay until you manually delete them.
In Java, when you use the keyword "new" you actually get back a pointer to an object. This is the only way to instantiate an object type in Java. So when you say you have an "Array of objects" in Java, it's more correct to say that you have an array of pointers to objects.
C++ does not hide the fact that objects are pointers. You can have a variable referencing an object, or you can have a variable referencing a pointer to an object.
In your example, you need to explicitly declare it an as an array of pointers to objects.
Players **players = new (Player*)[1]; // Create an array of player pointers
players[0] = new Player(playerWidth, playerHeight, 20, 1); // Create a single player
And while C++ allows you to explicitly create objects using the keyword new, you must be sure to cleanup your objects once you are done, otherwise they will never be deallocated (known as a memory leak).
This is one of the major differences between C++ and Java; Java's objects are garbage collected and the programmer doesn't have to worry about managing the lifetime of an object.
Once you are done, you will need to cleanup both the individual player that you allocated, as well as the array. Good rule of thumb is that every call to new should correspond to a call to delete.
delete players[0]; // delete the player pointed to by players[0]
delete[] players; // syntax for deleting arrays
However, something interesting to note is that unlike Java, where objects are allocated on the heap, you can create objects on the stack in C++ as if they were primitive types (like int, float, char). This allows you to have objects that are locally scoped, as well as contiguously aligned in memory. There is no way to do this in Java.
If you allocate an array of objects this way, then the default constructor is called for each object in the array.
Player p; // This calls the default constructor and returns a Player object
Players *players = new Player[5]; // Create an array of player objects
players[0].playerWidth = 8; // valid because the object has already been constructed
delete[] players; // don't forget to cleanup the array.
// no need to cleanup individual player objects, as they are locally scoped.
EDIT: As some others have mentioned, using a std::vector instead of an array is probably easier in your case (no need to worry about memory allocation) and is on the same order of performance as an array; however I think it is extremely important to become comfortable with the notion of pointers in C++ as they help you understand how memory is organized.
Here is the syntax for creating a vector of Player pointers.
std::vector<Player*> players(1); // Creates a vector of pointer to player with length 1
players[0] = new Player(playerWidth, playerHeight, 20, 1); // Create a new player object
delete players[0]; // delete the player
And the syntax for creating a vector of actual Player object instances (this one is the most preferred solution):
std::vector<Player> players(5); // Creates a vector of five player objects
players[0].playerWidth = 8; //already constructed, so we can edit immediately
//no cleanup required for the vector _or_ the players.