web-dev-qa-db-ja.com

C ++でのオブジェクトの割り当て

質問を文脈化するために、次の定義を持つMatrixクラスを使用しています。

Matrix(unsigned int, unsigned int); // matrix of the given dimension full of zeroes
Matrix(Matrix*); // creates a new matrix from another one
int &operator()(int, int);  // used to access the matrix
int **matrix; // the matrix

次に、次の2つのコードスニペットを取得します。

最初:

Matrix test(4,4);
Matrix ptr = test;
ptr(0,0) = 95;

2番目:

Matrix test(4,4);
Matrix *ptr = &test;
(*ptr)(0,0) = 95;

どちらのコードも同じ効果があり、(0,0)の位置にある要素は95を受け取ります(最初のスニペットはJavaに非常に似ているため、この質問をすることになりました)。問題は、どちらの方法でもオブジェクトの割り当てを正しく行うことができるかどうかです。

7
user1493813

これは少し複雑です。

この単純なクラスを考えてみましょう。

class Thing1
{
public:
  int n;
}

ここで、最初の実験を試みます。

Thing1 A;
A.n = 5;

Thing1 B = A;
B.n = 7;

cout << A.n << " " << B.n << endl;

結果は「57」です。 ABは、2つの別個の独立したオブジェクトです。一方を変更しても、もう一方は変更されません。

2番目の実験:

Thing1 *p = &A;
p->n = 9;

cout << A.n << " " << p->n << endl;

結果は「99」です。 pAへのポインタであるため、A.np->nは同じものです。

今、物事は複雑になります:

class Thing2
{
public:
  int *p;
};

...
Thing2 A;
A.p = new int(2);

Thing2 B = A;
*(B.p) = 4;

cout << *(A.p) << " " << *(B.p) << endl;

これで、結果は「44」になります。割り当てB = Apointerをコピーしたため、ABは2つの異なるオブジェクトですが、それらのポインターは同じintを指しています。これは浅いコピーです。一般に、ディープコピーを作成する場合(つまり、各Thingが独自のintを指す場合)、手動で行う必要があります。または、クラスに代入演算子を指定して処理します。 Matrixクラスには明示的な代入演算子がないため、コンパイラはデフォルトを指定します。これは浅いコピーです。そのため、最初のスニペットでは、両方のマトリックスが変更されているように見えます。

EDIT:Thing A=B;形式の宣言がコピーコンストラクターを使用することを指摘してくれた@AlisherKassymovに感謝します代入演算子。ではないので、上記のコードでソリューションを機能させるには、コピーコンストラクターがディープコピーを作成する必要があります。 (コピーコンストラクターがそれを行う場合は、ほぼ確実に代入演算子にもそれを行わせることに注意してください( つのルール を参照)。また、これらの関数が複雑になる場合は、単純にコピーコンストラクターに代入演算子を呼び出させます。)

25
Beta

最初は、testの値をptrにコピーします。 2つ目は、ptrをtestのアドレスへのポインタに設定します

これらの2つのアクションは同じではありません。最初のケースでは、ptrtestと同じ値になりますが、それらにはそのデータの2つの異なるコピーがあるため、ptr(0,0) = 95;の割り当てはtest(0, 0)を設定しません。

ただし、2番目のインスタンスでは、ptrtestのアドレスを指しているため、ptris testの逆参照です。したがって、ここで値を設定すると、実際にはtest(0, 0)の値も設定されます。

これは、次のようなプログラムで簡単に確認できます。

#include <iostream>
class Test{
public:
    int i;
};

int main(){
    Test c;
    c.i = 1;

    Test d = c;
    d.i = 2;
    std::cout << "Value: " << c.i << "\n";

    Test* e = &c;
    (*e).i = 2;
    std::cout << "Pointer: " << c.i << "\n";
}

もちろん、マトリックス(新規)を動的に割り当てる場合、最初の例のように値をコピーすると、データへのポインターもコピーされるため、新しいデータを設定すると、2番目の例と同じように見えます。 。

2
Sinkingpoint

この場合のJavaの動作に相当するものは、C++referencesを使用して表現されます。

Matrix test(4,4);
Matrix &ptr = test;
ptr(0,0) = 95;

このコードは実際にポインタバージョンと同じことを行います。つまり、元のtestオブジェクトを変更します。

最初のコードサンプルは、元のオブジェクトのcopyを正式に作成してから、そのコピーを変更します。ただし、Matrixクラスは、下位レベルのメモリ(matrixポインタ)を所有するマルチレベルオブジェクトのようです。 Matrixクラスのコピーコンストラクターが浅いコピーロジックを実装している場合(つまり、下位レベルの行列データをディープコピーではなく元のオブジェクトと共有している場合)、コピーを変更すると、元のオブジェクトが次のように変更されます。上手。この動作が正しいか正しくないかは、意図によって異なります。

コメントの中で、最初のコードも元のオブジェクトを変更しているように見えるとおっしゃいました。これはすぐに、クラスが実際に浅いコピーロジックを実装していることを意味します。そして、それはあなたのデザインの意図された部分ではないように見えます。どうやらあなたはあなたがあなたのMatrixクラスを実装していたときに つのルール に従うのを忘れていたようです。

1
AnT