コピーコンストラクション関数のパラメータタイプは参照でなければなりません


C++では,コンストラクション関数,コピーコンストラクション関数,析出関数,賦値関数(賦値演算子リロード)が最も基本的な知識である.しかし、「構造関数のパラメータをコピーするには、なぜ参照タイプを使用する必要がありますか?」と聞かれたら、この質問にどう答えますか.メモリコピーを1回減らすために答えるかもしれません.恥ずかしいことに、私の第一感覚もそう答えた.でもまあ、考えてみると、この答えは間違っていることに気づきました.
理由:コピーコンストラクタのパラメータがCClass(const CClass c_class)のように参照されていない場合は、パス-by-valueを使用することに相当し、パスはクラスのコピーコンストラクタを呼び出し、コピーコンストラクタを無限に再帰的に呼び出すことになります.したがって、コピーコンストラクション関数のパラメータは参照でなければなりません.明らかにする必要があるのは、ポインタも実際には値を伝達し、上のコピー構造関数がCClass(const CClass*c_class)と書かれている場合でもだめです.実際、伝達参照のみが伝達値ではなく、他のすべての伝達方式が伝達値である.
まず小さな例から始めます:(自分でこのプログラムの出力をテストしてみてください.)
#include<iostream>
using namespace std;

class CExample
{
private:
	int m_nTest;

public:
	CExample(int x) : m_nTest(x)      //       
	{ 
		cout << "constructor with argument"<<endl;
	}

	//       ,    const       ,         
	CExample(const CExample & ex)     //      
	{
		m_nTest = ex.m_nTest;
		cout << "copy constructor"<<endl;
	}

	CExample& operator = (const CExample &ex)   //    (       )
	{	
		cout << "assignment operator"<<endl;
		m_nTest = ex.m_nTest;
		return *this;
	}

	void myTestFunc(CExample ex)
	{
	}
};

int main(void)
{
	CExample aaa(2);
	CExample bbb(3);
	bbb = aaa;
	CExample ccc = aaa;
	bbb.myTestFunc(aaa);

	return 0;	
}

この例の出力結果は、
constructor with argument        // CExample aaa(2);
constructor with argument        // CExample bbb(3);
assignment operator              // bbb = aaa;
copy constructor                 // CExample ccc = aaa;
copy constructor                 //  bbb.myTestFunc(aaa);

もしあなたが一目でこの結果を見ることができたら、おめでとうございます.立ち上がってお尻を捻って、これ以上下を見なくてもいいです.
もしあなたの結果と出力結果に誤差があれば、謙虚に読んでください.
最初の出力:constructor with argument//CExample aaa(2);
もしあなたが理解していないならば、ある人を探してあなたを引きずって痛打して、それから口の中でまた“私は二師兄で、私は二師兄です......”
2番目の出力:constructor with argument//CExample bbb(3);
分析は最初と同じ
3番目の出力:assignment operator//bbb=aaa;
4番目の出力:copy constructor//CExample ccc=aaa;
この二つは一緒に言わなければなりません.なぜ二つが一致しないのかと聞かれるに違いない.なぜなら、bbbオブジェクトはインスタンス化されており、構築する必要はありません.このときaaaをbbbに割り当てるだけで、付与関数を呼び出すだけで、こんなに簡単で、まだ分からないなら、壁にぶつかって行きます.しかしcccはまだインスタンス化されていないので、呼び出したのはコピー構造関数で、付与関数ではなくcccを構築して、まだ分からないなら、私は壁にぶつかって行きます!!
 
5番目の出力:copy constructor//bb.myTestFunc(aaa);
実際にはaaaがパラメータとしてbbbに伝達する.myTestFunc(CExample ex)、すなわちCExample ex=aaa;4つ目と一致しているので、付与関数ではなく構造関数をコピーします.もしまだ分からないなら、私の頭はさっき血を流していました.もう私をぶつけないでください.自分でもう一度詰めてください.
この例では,構造関数をコピーするパラメータが参照タイプしか使用できない理由を解析する.
4番目の出力を見てください:copy constructor//CExample ccc=aaa;
構造cccは、実質的にcccである.CExample(aaa); コピー構造関数パラメータが参照タイプでない場合、ccc.CExample(aaa)はaaaになってcccに伝達する.CExample(CExample ex)、すなわちCExample ex=aaaは、exが初期化されていないため、CExample ex=aaaはコピーコンストラクション関数を呼び出し続け、次にex、すなわちex.CExample(aaa)を構築し、必然的にaaaがCExample(CExample ex)、すなわちCExample ex=aaaに渡される.コピーコンストラクション関数がトリガーされ、永遠に再帰されます.
したがって、あんなに大きなカーブを曲がると、コピーコンストラクション関数のパラメータが参照タイプを使用するのは、メモリコピーを1回減らすためではなく、コピーコンストラクション関数の無制限な再帰を避けるためであることを説明したいということです.
なお、コピーコンストラクタは、a、明示的または暗黙的に同じタイプのオブジェクトで別のオブジェクトを初期化する場合に呼び出されます.前例のように、dをオブジェクトcで初期化する.b、実パラメータとして関数に渡す.CClass(const CClass c_class)では、CClassのコピーコンストラクタが呼び出されます.c、関数内にオブジェクトを返すと、戻り値タイプのコピーコンストラクタも呼び出されます.d、シーケンスコンテナ内の要素を初期化する場合.例えばvectorsvec(5)ではstringのデフォルトコンストラクション関数とコピーコンストラクション関数が呼び出されます.e、配列要素をリストで初期化する場合.string a[] = {string(“hello”), string(“world”)}; stringのコピーコンストラクション関数が呼び出されます.コンストラクタが明示的に宣言されていない場合、コンパイラはクラスにデフォルトのコンストラクタを合成します.クラスにコンストラクション関数が宣言されている場合、コンパイラはクラスのデフォルトのコンストラクション関数を合成することを阻止します.コンストラクション関数とは異なり、他のコンストラクション関数が定義されていても(コピーコンストラクション関数が定義されていない)、コンパイラは常にコピーコンストラクション関数を合成します.また,関数の戻り値が参照であるか否かにも大きな違いがあり,参照でない場合は単純なオブジェクトであるため,コピーコンストラクタを呼び出す必要があり,そうでなければ,参照であればコピーコンストラクタを呼び出す必要はない.
#include<iostream>
using namespace std;

class A
{
private:
	int m_nTest;
public:
	A()
	{
	}
	A(const A& other)    //      
	{
		m_nTest = other.m_nTest;
		cout << "copy constructor"<<endl;  
	}
	A & operator =(const A& other)
	{
		if(this != &other)
		{
			m_nTest = other.m_nTest;
			cout<<"Copy Assign"<<endl;
		}
		return *this;
	}
};

A fun(A &x)
{
	return x;     //          ,          
}

int main(void)
{
	A test;
	fun(test);
	system("pause");
	return 0;
}

笔记试験のテーマを分かち合って、下の図の中のC++コードをコンパイルして実行して、结果は何ですか?(A)コンパイルエラー;(B)コンパイルに成功し、実行時にプログラムがクラッシュした.(C)コンパイルは正常に動作し、出力は10である.正しい答えを選んで原因を分析してください.
class A
{
private:
	int value;
public:
	A(int n)
	{
		value = n;
	}

	A(A other)
	{
		value = other.value;
	}
	void Print()
	{
		cout<<value<<endl;
	}
};

int main(void)
{
	A a = 10;
	A b = a;
	b.Print();
	return 0;
}

答え:コンパイルエラー.複製コンストラクション関数に入力されるパラメータは、Aの一例である.パス値であるため、パラメータを実パラメータにコピーすると、レプリケーションコンストラクション関数が呼び出されます.したがって、コンストラクション関数の値伝達をコピーできるようにすると、永続的な再帰が形成され、スタックオーバーフローが発生します.したがって、C++の基準では、構造関数の値伝達パラメータのコピーは許可されず、参照または定数参照でなければなりません.Visual StudioとGCCでは、コンパイルエラーが発生します.