ここでは、クラスStringを使用してこれら2つの関数を紹介します。
コピーコンストラクターは単一の仮パラメーターを持つ特別なコンストラクターであり、仮パラメーター(一般的に使用されるconst変更)はクラスタイプへの参照です。新しいオブジェクトが定義され、同じタイプのオブジェクトで初期化されると、コピーコンストラクターが明示的に使用されます。仮パラメーターがこの型への参照である必要があるのはなぜですか?仮パラメーターがこのクラスのインスタンスである場合、それは値によるパラメーターであるため、仮パラメーターを実際のパラメーターにコピーし、コピーコンストラクターを呼び出します。コピーコンストラクターが値を渡すことが許可されている場合、コピーはコピーコンストラクターで呼び出されます。コンストラクタ。無限の再帰呼び出しを形成し、スタックオーバーフローを引き起こします。
string(const string &s);
//类成员,无返回值
割り当て関数も割り当て演算子によってオーバーロードされます。これは、割り当てがクラスのメンバーである必要があるため、その最初のオペランドが暗黙的にthisポインターにバインドされます。つまり、これは左オペランドへのポインターにバインドされます。したがって、代入演算子は単一の仮パラメーターを受け入れ、仮パラメーターは同じタイプのオブジェクトです。右側のオペランドは、通常、const参照として渡されます。
string(const string &s);
//类成员,无返回值
コピーコンストラクターと代入関数は、すべてのオブジェクトで使用されるわけではありません。また、それらがアクティブに記述されていない場合、コンパイラーはデフォルトの関数を「ビットコピー」方式で自動的に生成します。クラスの設計では、「ビットコピー」を防止する必要があります。クラスにポインター変数が含まれている場合、これらの2つのデフォルト関数は失敗します。これには、深いコピーと浅いコピーが含まれます。
コピーには、ディープコピー、シャローコピーの 2種類があります。
クラスの等号割り当てが表示されると、コピー関数が呼び出されます。表示コピーコンストラクタが定義されていない場合、システムはデフォルトのコピー関数、つまりシャローコピーを呼び出します。メンバーのコピーを1つずつ記入してください。データメンバーにポインターがない場合は、浅いコピーが可能です。
ただし、データメンバーにポインターがある場合、単純な浅いコピーが使用されると、2つの型の2つのポインターは同じアドレスを指し、オブジェクトが終了しようとすると、デストラクタが2回呼び出され、ポインターがハングします。したがって、現時点では、ディープコピーを使用する必要があります。
ディープコピーとシャローコピーの違いは、データを格納するためにヒープメモリの追加スペースにディープコピーが適用されることです。これにより、ポインターの中断の問題も解決されます。別のメモリスペースを指しますが、内容は同じです。
つまり、データメンバーにポインターがある場合は、ディープコピーを使用する必要があります。
class A{
char * c;
}a, b;
//浅复制不会重新分配内存
//将a 赋给 b,缺省赋值函数的“位拷贝”意味着执行
a.c = b.c;
//从这行代码可以看出
//b.c 原有的内存没有释放
//a.c 和 b.c 指向同一块内存,任何一方的变动都会影响到另一方
//对象析构的时候,c 被释放了两次(a.c == b.c 指针一样)
//深复制需要自己处理里面的指针
class A{
char *c;
A& operator =(const A &b)
{
//隐含 this 指针
if (this == &b)
return *this;
delete c;//释放原有内存资源
//分配新的内存资源
int length = strlen(b.c);
c = new char[length + 1];
strcpy(c, b.c);
return *this;
}
}a, b;
//这个是深复制,它有自定义的复制函数,赋值时,对指针动态分配了内存
以下は、ディープコピーとシャローコピーの具体的な違いの概要です。
1.コピーオブジェクトの状態に他のオブジェクトへの参照が含まれている場合、参照オブジェクトが指すコンテンツをコピーする必要がある場合は、メモリアドレスを参照するのではなく、ディープコピーです。それ以外の場合は、浅いコピーです。
2.浅いコピーはメンバーデータ間の割り当てであり、値がコピーされると、2つのオブジェクトは共通のリソースを持ちます。ディープコピーでは、最初にリソースをコピーします。オブジェクトには異なるリソース(メモリ領域)がありますが、リソースの内容(メモリ内のデータ)は同じです。
3.浅いコピーとは異なり、ディープコピーが参照を処理する場合、新しいオブジェクトのコンテンツを変更しても、元のオブジェクトのコンテンツには影響しません
。4。深いコピーとは異なり、浅いコピーの後でリソースが解放されると、リソースの属性が不明確になる場合があります。 (ポインタが含まれていると、一方のパーティのリソースが解放され、実際には他方のパーティのリソースもそれに応じて解放されます)。これにより、プログラムが正しく実行されません。
ディープコピーとシャローコピーのもう1つの違いは、シャローコピーを実行すると、メモリアドレスが直接コピーされるため、ディープコピーは同じサイズのメモリ領域を再度開き、リソース全体をコピーする必要があることです。
さて、前の予告で、コピーコンストラクターと代入関数について話しましょう。実際、最初の部分では、
ここでは例として文字列クラスを取ります
class String
{
public:
String(const char *str = NULL);
String(const String &rhs);
String& operator=(const String &rhs);
~String(void){
delete[] m_data;
}
private:
char *m_data;
};
//构造函数
String::String(const char* str)
{
if (NULL == str)
{
m_data = new char[1];
*m_data = '\0';
}
else
{
m_data = new char[strlen(str) + 1];
strcpy(m_data, str);
}
}
//拷贝构造函数,无需检验参数的有效性
String::String(const String &rhs)
{
m_data = new char[strlen(rhs.m_data) + 1];
strcpy(m_data, rhs.m_data);
}
//赋值函数
String& String::operator=(const String &rhs)
{
if (this == &rhs)
return *this;
delete[] m_data; m_data = NULL;
m_data = new char[strlen(rhs.m_data) + 1];
strcpy(m_data, rhs.m_data);
return *this;
}
Stringのようなコピーコンストラクターと通常のコンストラクターの違いは、 "参照"をNULLにすることはできず、 "ポインター"をNULLにすることができるため、関数エントリでNULLと比較する必要がないことです。(これは参照とポインタの重要な違いです)。次に、深いコピーに注意を払う必要があります。
それに比べて、Stringクラスの代入関数ははるかに複雑です。
-
まず、自己割り当てのチェックを実行する必要があります。
これは、b = a; c = b; a = c;などの自己コピーと間接コピーを防ぐためです。コピー操作も失敗するため、これは重要なステップです。また、セルフテストでは内容ではなくアドレスがチェックされ、メモリアドレスは一意であることに注意してください。if this(= =&rhs) -
元のメモリリソースを解放するには、
deleteを使用して元のメモリリソースを解放する必要があります。現時点で解放しないと、この変数が指すメモリアドレスは元のメモリアドレスではなくなり、メモリを解放できなくなり、メモリリークが発生します。 -
新しいメモリリソースを割り当て、リソースをコピーして
、変数が指すメモリアドレスを変更しますが、内部のリソースは同じです -
このオブジェクトへの参照を返すこれの
目的は、a = b = c;のようなチェーン式を実現することです。
ただし、慎重に検討してください。上記のプログラムは例外の安全性を考慮していません。メモリを割り当てる前に、deleteを使用して元のインスタンスのメモリを解放します。後で例外をスローするのに十分なメモリがない場合、前の削除のm_dataはnullポインタになります。 、これによりプログラムがクラッシュするのは非常に簡単です。そのため、順序を変更できます。つまり、最初にインスタンスメモリを削除し、次にdeleteを使用して元のメモリ領域を解放し、最後にm_dataを使用して新しいポインタを割り当てます。
次に、コピーコンストラクターと代入関数の違いについて説明します。
コピーコンストラクターと代入関数は非常に混乱しやすく、多くの場合、誤字や誤用につながります。オブジェクトの作成時にコピーコンストラクターが呼び出されますが、割り当て関数は既存のオブジェクトに対してのみ呼び出すことができます。以下のコードを見てください:
String a("hello");
String b("world");
String c = a;//这里c对象被创建调用的是拷贝构造函数
//一般是写成 c(a);这里是与后面比较
c = b;//前面c对象已经创建,所以这里是赋值函数
上記は、「=」が割り当て関数(算術オーバーロード関数)と必ずしも呼ばれない場所を示しています。コンストラクターをコピーすることもできるので、コピーコンストラクターはいつ呼び出され、いつ割り当て関数が呼び出されますか?判断基準は実際には非常に単純です。一時変数が初めて表示される場合は、コピーコンストラクターのみを呼び出すことができます。それ以外の場合、変数が既に存在する場合は、割り当て関数が呼び出されます。