エラーが言っているのは、間違った型の値を変数に代入しようとしているということです。エラーが示す場合Player = Player *
、左側の変数が aPlayer
で、右側の値が a であることを意味しますPlayer *
。
players[0] = new Player(playerWidth, playerHeight, 20, 1);
問題は、次の場合と似ています。
int x;
x = "Hello, World!";
左手と右手の型が一致せず、自然な変換が行われないため、エラーが発生します。
最初の問題は、あなたが Java のバックグラウンドを持っていることです。Java はポインターをよく使用しますが、それらを隠しています。C++ はそれらをまったく隠しません。その結果、C++ ではポインターを明示的に処理するための構文が異なります。Java はそれをすべて取り除き、ほとんどの場合、ポインターを処理するために C++ からの通常の非ポインター構文を使用しました。
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;
ポインターを操作することと、オブジェクトを直接操作することの違いを理解することは非常に重要です。
Java: C++:
Player a = new Player(); Player *a = new Player();
Player b = a; Player *b = a;
b.foo(); b->foo();
a
このコードにはオブジェクトが 1 つしかなく、どちらからでもアクセスできますがb
、違いはなく、a
どちらb
も同じオブジェクトへのポインターです。
C++:
Player c = Player();
Player d = c;
d.foo();
このコードには 2 つのオブジェクトがあります。それらは別個のものであり、何かを行っても にd
は影響しませんc
。
Java で「プリミティブ」型int
と Object 型の違いについて学んだ場合、String
それについて考える 1 つの方法は、C++ ではすべてのオブジェクトがプリミティブであるということです。あなたのコードを振り返って、この「C++ オブジェクトは Java プリミティブのようなものである」というルールを使用すると、何が問題なのかがよくわかるかもしれません。
Java:
int[] players = new int[1];
players[0] = new int(playerWidth); // huh???
これにより、割り当ての右側は、新しいプレーヤー オブジェクトの動的割り当てではなく、単純に Player 値にする必要があることが明確になります。Java の int の場合、これはplayers[0] = 100;
. Java のオブジェクト型は異なるため、Java には、値を書き込む方法でオブジェクト値を書き込む方法がありませんint
。しかし、C++ はそうです。players[0] = Player(playerWidth, playerHeight, 20, 1);
2 番目の問題は、C の配列が奇妙で、C++ がそれを継承していることです。
C および C++ のポインターを使用すると、'ポインター演算が可能になります。オブジェクトへのポインターがある場合は、それに加算または減算して、別のオブジェクトへのポインターを取得できます。Javaにはこれに似たものはありません。
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
さらに、配列インデックス演算子[]
はポインターで機能します。x[5]
と同等*(x+5)
です。これは、ポインターを配列として使用できることを意味します。これは慣用的であり、C および C++ で期待されています。実際、C++ にも焼き付けられています。
C++ ではnew
、オブジェクトを動的に割り当てるために使用する場合、たとえばnew Player
、通常、指定した型へのポインターを取得します。この例では、 が得られPlayer *
ます。しかし、たとえばnew Player[5]
、配列を動的に割り当てる場合は異なります。5 の配列へのポインターを取得する代わりに、Players
実際には最初の要素へのポインターを取得します。これは他のものと同じですPlayer *
:
Player *p = new Player; // not an array
Player *arr = new Player[5]; // an array
このポインターが異なる唯一の点は、ポインター演算を実行すると、有効なPlayer
オブジェクトへのポインターが取得されることです。
Player *x = p + 1; // not pointing at a valid Player
Player *y = arr + 3; // pointing at the fourth array element
new
保護せずに使用するとdelete
、正しく使用するのが難しくなります。これを実証するには:
int *x = new int;
foo();
delete x;
このコードはエラーが発生しやすく、おそらく間違っています。具体的にfoo()
は、例外がスローされた場合、x
リークされます。
C++ では、後でnew
呼び出す責任を取得するなど、責任を取得するたびdelete
に、次のことを覚えておく必要があります。
RAII の
責任* 取得は初期化です
* 「リソースの取得は初期化です」とよく言われますが、リソースは 1 つの責任にすぎません。Jon Kalb のException Safe C++の講演の 1 つで、後者の用語を使用するよう説得されました。
RAII は、責任を取得するたびに、オブジェクトを初期化しているように見える必要があることを意味します。具体的には、その責任を管理することを目的とする特別なオブジェクトを初期化しています。このような型の 1 つの例は、 で割り当てられたstd::unique_ptr<int>
へのポインタを管理するものです。int
new
C++:
std::unique_ptr<int> x(new int);
foo();
// no 'delete x;'
Player
配列を管理するには、次のstd::unqiue_ptr
ように使用します。
std::unique_ptr<Player[]> players(new Player[1]);
players[0] = Player(playerWidth, playerHeight, 20, 1);
これで、unique_ptr
がその割り当てを処理するので、自分自身を呼び出す必要はありませんdelete
。(NB 配列を割り当てるときはunique_ptr
、配列型を指定する必要があります; 。std::unique_ptr<Player[]>
他のものを割り当てるときは、非配列型を使用しますstd::unique_ptr<Player>
。)
もちろん、C++ には、配列を管理するためのさらに特殊な RAII 型がstd::vector
あり、 を使用するよりも、それを使用することをお勧めしますstd::unique_ptr
。
std::vector<Player> players(1);
players[0] = Player(playerWidth, playerHeight, 20, 1);
または C++11 の場合:
std::vector<Player> players { Player(playerWidth, playerHeight, 20, 1) };