C++コピーコンストラクタ(コピーコンストラクタ)
4608 ワード
一、コピー構造関数の形式
コンストラクション関数のコピーは、コンストラクション関数の特殊な状況です.クラスのオブジェクトには様々なメンバー変数が含まれているため、コピーが発生したときに通常のオブジェクトと同じようにコピーすることはできません.そのため、コピーコンストラクション関数を使用してオブジェクトコピーを行う必要があります.コピーコンストラクション関数には1つのパラメータしかありません.パラメータタイプはこのクラスの参照です.
コンストラクション関数が明示的に定義されていない場合、コンパイラは自動的にコピーコンストラクション関数を生成します.ほとんどの場合、ターゲットオブジェクトの各メンバー変数がソースオブジェクトと等しくなっても、ソースオブジェクトからターゲットオブジェクトへの1バイトずつのコピーを実現する役割を果たします.
なぜパラメータが1つしかないのですか?
通常、コピーを作成するには、2つのパラメータが必要です.しかし、クラスにthisポインタが存在することを知っています.したがって、パラメータの1つはthisポインタが指すオブジェクトです.このオブジェクトは暗黙的に定義されているので、コピーコンストラクション関数では、thisポインタがオブジェクトを指す別のオブジェクトを初期化する必要があります.少し迂回しているように聞こえますが、簡単に言えば、あるオブジェクトで別のオブジェクトを初期化すると、コピーコンストラクション関数が呼び出され、この関数を呼び出すオブジェクトはthisポインタが指すオブジェクトとして、このオブジェクトを初期化するために使用されるオブジェクトはコピーコンストラクション関数に実パラメータとして渡さなければなりません.したがって、コピーコンストラクション関数では、オブジェクトのパラメータを受信する必要があります.
ここでいうパラメータは、本当に1つのパラメータに限られているわけではないことに注意してください.オブジェクトコピーのプロシージャは1対1のプロシージャであるため、ここでのパラメータはクラスで定義されたオブジェクトを指します.
クラスQuickの場合、コンストラクション関数の最初のパラメータが次のいずれかである場合. Quick& const Quick& volatile Quick& const volatile Quick&
他のパラメータや他のパラメータにデフォルト値がない場合、この関数はコピー構造関数である.
例えばQuick(const Quick&,int m=3)もコピーコンストラクタ
パラメータタイプがこのクラスの参照である理由
コピーコンストラクション関数を呼び出すと、値で渡すと仮定し、クラスオブジェクトの値を渡すと、まず一時コピーが行われます.クラスオブジェクトのコピーは一時コピー関数を呼び出すため、また値転送が使用されるため、再び一時コピーが発生し、無限再帰が行われます.このようなことを避けるために、クラスの参照方法を使用します.
二、コピーコンストラクタを呼び出すタイミング
コピーコンストラクタを呼び出すには、次の3つのケースがあります.
1.同じクラスの別のオブジェクトを1つのオブジェクトで初期化すると、コピーコンストラクタが呼び出されます.
ここで注意しなければならないのは
上記の3番目の文は、初期化文ではなく、付与文です.左のオペランドはすでに定義された変数であるため、コピーコンストラクション関数は呼び出されません.
2.関数funのパラメータがクラスQuickのオブジェクトである場合、funが呼び出されると、クラスQuickのコピーコンストラクタが呼び出される
3.関数の戻り値がクラスQuickのオブジェクトである場合、関数の戻り値がクラスQuickのコピーコンストラクタが呼び出される
三、浅いコピーと深いコピー
1.浅いコピー
多くの前コピーでは、コピーコンストラクタを明示的に定義しない場合、コンパイラは自動的にコピーコンストラクタを生成します.このコンパイラは、デフォルトのコピーコンストラクタを生成し、オブジェクトを関数パラメータに渡すか、関数を返すオブジェクトに対してよく完了します.このデフォルトのコピーコンストラクタは、浅いコピーとも呼ばれます.このコンストラクション関数は簡単で、ほとんどの場合、ソースオブジェクトからターゲットオブジェクトへの1バイト単位のコピーを実現し、ターゲットオブジェクトの各メンバー変数がソースオブジェクトと等しくなっても機能します.
では、なぜコピー構造関数を明示的に定義するのでしょうか.次のコードを見てみましょう.
コードが解決する問題は、クラス定義のオブジェクトの数を取得することです.
上記のコードでは、コピーコンストラクタを明示的に定義していません.コンパイラがデフォルトで生成したコピーコンストラクタを使用して、あるオブジェクトを使用して別のオブジェクトを初期化すると、コピーコンストラクタが呼び出されます.デフォルトで生成されたコピーコンストラクション関数を呼び出して初期化されたオブジェクトに対しては、オブジェクトカウント用のcountは操作されないため、この場合2つのオブジェクトがあるがcountの値は1であり、コンストラクション関数が呼び出されるとcountの値は-1である.
この結果、コピーコンストラクション関数は、静的データ・メンバーに対して対応する処理を行わなかったためです.
クラスに明示的なコピー構造関数を追加すれば、この問題を解決できます.
2.深いコピー
上記の問題を解決するコピー構造関数は浅いコピーですが、深いコピーと浅いコピーの違いはどこですか?
まず、次のコードを見てみましょう.
s 1を使用してs 2呼び出しコピーコンストラクション関数を初期化するには、s 1のコンテンツをs 2の空間にコピーすることを目的としていますが、実際には、浅いコピーを使用する方法は、s 2の指向を変更し、s 2をs 1が指す空間に指向させるだけで、構造関数を呼び出すときに同じ空間が2回解放され、エラーが発生します.また,従来のs 2指向の空間は解放されず,メモリが漏洩した.この問題を解決するために、クラス内のコピー構造関数を変更し、次のように変更する必要があります.
すなわち,オブジェクトをコピーする前に,まず空間割り当てを行う.
四、まとめ
初期化オブジェクトの関数には、コンストラクション関数とコピーコンストラクション関数(浅いコピー、深いコピー)の2種類があります.オブジェクトを異なる方法で初期化する場合は、関数がそれであることを明確に呼び出し、必要に応じてどの構造関数を使用するかを決定する必要があります.割り当て演算子を再ロードする場合も、コピーに注意する必要があります.
コンストラクション関数のコピーは、コンストラクション関数の特殊な状況です.クラスのオブジェクトには様々なメンバー変数が含まれているため、コピーが発生したときに通常のオブジェクトと同じようにコピーすることはできません.そのため、コピーコンストラクション関数を使用してオブジェクトコピーを行う必要があります.コピーコンストラクション関数には1つのパラメータしかありません.パラメータタイプはこのクラスの参照です.
コンストラクション関数が明示的に定義されていない場合、コンパイラは自動的にコピーコンストラクション関数を生成します.ほとんどの場合、ターゲットオブジェクトの各メンバー変数がソースオブジェクトと等しくなっても、ソースオブジェクトからターゲットオブジェクトへの1バイトずつのコピーを実現する役割を果たします.
なぜパラメータが1つしかないのですか?
通常、コピーを作成するには、2つのパラメータが必要です.しかし、クラスにthisポインタが存在することを知っています.したがって、パラメータの1つはthisポインタが指すオブジェクトです.このオブジェクトは暗黙的に定義されているので、コピーコンストラクション関数では、thisポインタがオブジェクトを指す別のオブジェクトを初期化する必要があります.少し迂回しているように聞こえますが、簡単に言えば、あるオブジェクトで別のオブジェクトを初期化すると、コピーコンストラクション関数が呼び出され、この関数を呼び出すオブジェクトはthisポインタが指すオブジェクトとして、このオブジェクトを初期化するために使用されるオブジェクトはコピーコンストラクション関数に実パラメータとして渡さなければなりません.したがって、コピーコンストラクション関数では、オブジェクトのパラメータを受信する必要があります.
ここでいうパラメータは、本当に1つのパラメータに限られているわけではないことに注意してください.オブジェクトコピーのプロシージャは1対1のプロシージャであるため、ここでのパラメータはクラスで定義されたオブジェクトを指します.
class Quick{
public:
Quick(int rear=1, int imag=1)
{
_rear = rear;
_imag = imag;
}
Quick(const Quick& ret)
{
_rear = ret._rear;
_imag = ret._imag;
cout << "copy" << endl;
}
private:
int _rear;
int _imag;
};
int main()
{
Quick s1;
Quick s2(s1);// s2
return 0;
}
クラスQuickの場合、コンストラクション関数の最初のパラメータが次のいずれかである場合.
他のパラメータや他のパラメータにデフォルト値がない場合、この関数はコピー構造関数である.
例えばQuick(const Quick&,int m=3)もコピーコンストラクタ
パラメータタイプがこのクラスの参照である理由
コピーコンストラクション関数を呼び出すと、値で渡すと仮定し、クラスオブジェクトの値を渡すと、まず一時コピーが行われます.クラスオブジェクトのコピーは一時コピー関数を呼び出すため、また値転送が使用されるため、再び一時コピーが発生し、無限再帰が行われます.このようなことを避けるために、クラスの参照方法を使用します.
二、コピーコンストラクタを呼び出すタイミング
コピーコンストラクタを呼び出すには、次の3つのケースがあります.
1.同じクラスの別のオブジェクトを1つのオブジェクトで初期化すると、コピーコンストラクタが呼び出されます.
Quick s1;
Quick s2(s1);
ここで注意しなければならないのは
Quick s1;
Quick s2;
s2=s1;
上記の3番目の文は、初期化文ではなく、付与文です.左のオペランドはすでに定義された変数であるため、コピーコンストラクション関数は呼び出されません.
2.関数funのパラメータがクラスQuickのオブジェクトである場合、funが呼び出されると、クラスQuickのコピーコンストラクタが呼び出される
int main()
{
fun(Quick ret);
return 0;
}
3.関数の戻り値がクラスQuickのオブジェクトである場合、関数の戻り値がクラスQuickのコピーコンストラクタが呼び出される
Quick Fun() {
Quick ret(2,3);
return ret;
}
三、浅いコピーと深いコピー
1.浅いコピー
多くの前コピーでは、コピーコンストラクタを明示的に定義しない場合、コンパイラは自動的にコピーコンストラクタを生成します.このコンパイラは、デフォルトのコピーコンストラクタを生成し、オブジェクトを関数パラメータに渡すか、関数を返すオブジェクトに対してよく完了します.このデフォルトのコピーコンストラクタは、浅いコピーとも呼ばれます.このコンストラクション関数は簡単で、ほとんどの場合、ソースオブジェクトからターゲットオブジェクトへの1バイト単位のコピーを実現し、ターゲットオブジェクトの各メンバー変数がソースオブジェクトと等しくなっても機能します.
では、なぜコピー構造関数を明示的に定義するのでしょうか.次のコードを見てみましょう.
class Hyottoko {
public:
Hyottoko()
{
m_count++;
}
static int getCount()
{
return m_count;
}
~Hyottoko()
{
m_count--;
}
private:
int *m_next;
int m_data;
static int m_count;
};
int Hyottoko::m_count = 0;
int main()
{
Hyottoko s1;//
Hyottoko s2(s1);//
cout << s2.getCount()<< endl;
system("pause");
return 0;
}
コードが解決する問題は、クラス定義のオブジェクトの数を取得することです.
上記のコードでは、コピーコンストラクタを明示的に定義していません.コンパイラがデフォルトで生成したコピーコンストラクタを使用して、あるオブジェクトを使用して別のオブジェクトを初期化すると、コピーコンストラクタが呼び出されます.デフォルトで生成されたコピーコンストラクション関数を呼び出して初期化されたオブジェクトに対しては、オブジェクトカウント用のcountは操作されないため、この場合2つのオブジェクトがあるがcountの値は1であり、コンストラクション関数が呼び出されるとcountの値は-1である.
この結果、コピーコンストラクション関数は、静的データ・メンバーに対して対応する処理を行わなかったためです.
クラスに明示的なコピー構造関数を追加すれば、この問題を解決できます.
Hyottoko(const Hyottoko& data)
{
m_count++;
}
2.深いコピー
上記の問題を解決するコピー構造関数は浅いコピーですが、深いコピーと浅いコピーの違いはどこですか?
まず、次のコードを見てみましょう.
class String {
public:
String(char*str = NULL)
{
m_str = new char[strlen(str)+1];
strcpy(m_str, str);
}
String(String& str)
{
strcpy(m_str, str.m_str);
}
~String()
{
if (m_str)
delete[]m_str;
}
private:
char *m_str;
};
int main()
{
String s1("Hello");
String s2(s1);
system("pause");
return 0;
}
s 1を使用してs 2呼び出しコピーコンストラクション関数を初期化するには、s 1のコンテンツをs 2の空間にコピーすることを目的としていますが、実際には、浅いコピーを使用する方法は、s 2の指向を変更し、s 2をs 1が指す空間に指向させるだけで、構造関数を呼び出すときに同じ空間が2回解放され、エラーが発生します.また,従来のs 2指向の空間は解放されず,メモリが漏洩した.この問題を解決するために、クラス内のコピー構造関数を変更し、次のように変更する必要があります.
String(String& str)
{
m_str = new char[strlen(str.m_str)+1];
strcpy(m_str, str.m_str);
}
すなわち,オブジェクトをコピーする前に,まず空間割り当てを行う.
四、まとめ
初期化オブジェクトの関数には、コンストラクション関数とコピーコンストラクション関数(浅いコピー、深いコピー)の2種類があります.オブジェクトを異なる方法で初期化する場合は、関数がそれであることを明確に呼び出し、必要に応じてどの構造関数を使用するかを決定する必要があります.割り当て演算子を再ロードする場合も、コピーに注意する必要があります.