web-dev-qa-db-ja.com

代入演算子とコピーコンストラクターC ++

C++での基本的なポインタの理解をテストするための次のコードがあります。

// Integer.cpp
#include "Integer.h"
Integer::Integer()
{
  value = new int;
  *value = 0;
}

Integer::Integer( int intVal )
{
  value = new int;
  *value = intVal;
} 

Integer::~Integer()
{
  delete value;
}

Integer::Integer(const Integer &rhInt)
{
  value = new int;
  *value = *rhInt.value;
}

int Integer::getInteger() const
{
  return *value;
}

void Integer::setInteger( int newInteger )
{
  *value = newInteger;
}

Integer& Integer::operator=( const Integer& rhInt )
{   
  *value = *rhInt.value;
  return *this;
}

// IntegerTest.cpp
#include <iostream>
#include <cstdlib>
#include "Integer.h"

using namespace std;

void displayInteger( char* str, Integer intObj )
{
  cout << str << " is " << intObj.getInteger() << endl;
}

int main( int argc, char* argv[] )
{
 Integer intVal1;
 Integer intVal2(10);

 displayInteger( "intVal1", intVal1 );
 displayInteger( "intVal2", intVal2 );

 intVal1 = intVal2;

 displayInteger( "intVal1", intVal1 );

 return EXIT_SUCCESS;
}

このコードは期待どおりに機能し、次のように出力します。

intVal1 is 0

intVal2 is 10

intVal1 is 10

ただし、コピーコンストラクターを削除すると、次のように出力されます。

intVal1 is 0

intVal2 is 10

intVal1 is 6705152

なぜそうなのかわかりません。私の理解では、コピーコンストラクターは、存在しないオブジェクトへの割り当ての場合に使用されます。ここに intVal1は存在するのに、なぜ代入演算子が呼び出されないのですか?

20
Lukas Bystricky

コピーコンストラクターは、割り当て中には使用されません。 displayInteger関数に引数を渡すときに、あなたのケースのコピーコンストラクターが使用されます。 2番目のパラメーターは値で渡されます。つまり、コピーコンストラクターによって初期化されます。

コピーコンストラクターのバージョンは、クラスが所有するデータのdeepコピーを実行します(割り当て演算子と同じように)。そのため、コピーコンストラクタのバージョンですべてが正しく機能します。

独自のコピーコンストラクターを削除すると、コンパイラーはそれを暗黙的に生成します。コンパイラーが生成したコピーコンストラクターは、オブジェクトのshallowコピーを実行します。これは "Rule of Three" に違反し、クラスの機能を破壊します。これは、実験で観察したとおりです。基本的に、displayIntegerへの最初の呼び出しはintVal1オブジェクトに損傷を与え、displayIntegerへの2番目の呼び出しはintVal2オブジェクトに損傷を与えます。その後、両方のオブジェクトが壊れます。そのため、3番目のdisplayInteger呼び出しはガベージを表示します。

displayIntegerの宣言を次のように変更した場合

void displayInteger( char* str, const Integer &intObj )

明示的なコピーコンストラクターがなくても、コードは "動作"します。しかし、いかなる場合でも "Rule of Three" を無視することはお勧めできません。この方法で実装されたクラスは、「3つのルール」に従うか、コピー不可にする必要があります。

19
AnT

発生している問題は、デフォルトのコピーコンストラクターによって引き起こされます。これは、ポインターをコピーしますが、新しく割り当てられたメモリに関連付けません(コピーコンストラクターの実装のように)。オブジェクトを値で渡すと、コピーが作成され、実行がスコープ外になると、このコピーは破棄されます。デストラクタからのdeleteは、intVal1オブジェクトのvalueポインタを無効にし、それをdangling pointerにして、逆参照しますこのうちundefined behaviorが発生します。

デバッグ出力は、コードの動作を理解するために使用できます。

class Integer {
public:

    Integer() {
      cout << "ctor" << endl;
      value = new int;
      *value = 0;
    }

    ~Integer() {
        cout << "destructor" << endl;
        delete value;
    }

    Integer(int intVal) {
      cout << "ctor(int)" << endl;
      value = new int;
      *value = intVal;
    } 

    Integer(const Integer &rhInt) {
      cout << "copy ctor" << endl;
      value = new int;
      *value = *rhInt.value;
    }

    Integer& operator=(const Integer& rhInt){   
      cout << "assignment" << endl;
      *value = *rhInt.value;
      return *this;
    }

    int *value;
};

void foo(Integer intObj) {
    cout << intObj.value << " " << *(intObj.value) << endl;
}

このコードの出力:

Integer intVal1;
Integer intVal2(10);

foo( intVal1 );
foo( intVal2 );

intVal1 = intVal2;

foo( intVal1 );

です:

俳優
ctor(int)
コクター
0x9ed4028 0
デストラクタ
コクター
0x9ed4038 10
デストラクター
割り当て
コクター
0x9ed4048 10
デストラクター
デストラクター
デストラクター

これは、値でオブジェクトを渡すときにコピーコンストラクタが使用されることを示しています。ただし、ここで注意する必要があるのは、関数から戻ったときに呼び出されるデストラクタです。コピーコンストラクターの実装を削除すると、出力は次のようになります。

俳優
ctor(int)
x8134008 0
デストラクター
0x8134018 10
デストラクター
割り当て
x8134008 135479296
デストラクター
デストラクター
デストラクター

最初のコピーが、後で3番目のコピーで使用されたのと同じポインター(0x8134008を指す)でdeleteを呼び出し、このダングリングポインターが指すメモリが使用されたことを示します。

4
LihO

この呼び出しについて考えてみましょう:

displayInteger( "intVal1", intVal1 );

intObjdisplayIntegerパラメータにintVal1のコピーを作成しています:

void displayInteger( char* str, Integer intObj )
{
  cout << str << " is " << intObj.getInteger() << endl;
}

そのコピーは、intVal1と同じintを指します。 displayIntegerが戻ると、intObjが破棄され、intが破棄され、intVal1のポインターが無効なオブジェクトを指します。その時点で値にアクセスしようとすると、すべてのベットがオフになります(A.K.A.未定義の動作)。 intVal2でも同様のことが起こります。

より一般的なレベルでは、コピーコンストラクターを削除すると、3つのルールに違反することになります。これは、通常、この種の問題につながります。

2
Vaughn Cato