MyStringクラスにコピーコンストラクタを追加し,下記のmain.cppを実行できるようにしなさい.
参照とは,変数の別名のことでした.課題1での参照の説明を思い出しましょう.
プログラミング言語において,関数呼び出し時の変数の渡し方には2種類あります.
「値渡し」は,変数のコピーを渡す,ということです.呼び出し先の関数内でコピーをいくらいじっても,呼び出し元の変数は変化しません.コピーを伴うので,大きな変数(オブジェクト)の場合は性能低下が生じる恐れがあります.
「参照渡し」は,変数の参照を渡す,ということです.変数の参照を渡す場合は,呼び出し先の関数内で変数の参照をいじった場合,呼び出し元の変数が変化します.コピーを伴わないので,大きな変数(オブジェクト)を渡しても性能面で問題は少ないです.
C++言語においては,参照を渡すことができます.一方,参照渡しが無いC言語では,ポインタを値渡しして,呼び出し先の関数内でポインタの中身を操作することで,呼び出し元の変数を操作していました.
また,値渡しと参照渡しは関数の戻り値にも適用でき,「値を返す関数」だけでなく「参照を返す関数」も作成することができます.値を返す関数では戻り値のコピーが呼び出し元に返されますが,参照を返す関数では戻り値の参照を渡すため,呼び出し元で戻り値の変数を操作できます.戻り値として参照を返す際には,関数が終了した後も存在している変数の参照を返す必要があります.関数内の一時変数(ローカル変数)の参照を返すと,不定な動作をしますので注意しましょう.
ということで,「参照」はC++言語の重要な機能の一つです.
C++においては,あるオブジェクトを別のオブジェクトにコピーする状況として,”代入”と”コピーによる初期化”の2つがあります. (代入の方法を指定するには,次回に学習する”代入演算子のオーバーロード”を行います.ここでは触れません.)
コピーコンストラクタを定義すると,単純なコピーではない,「コピーによる初期化」の方法を指定することができます. コピーコンストラクタは,引数として同じクラスのオブジェクトの参照をとるコンストラクタで,下記の形式で記述されます.
classname(const classname& ref) { // 定義部(メンバ変数のコピー方法等を記述する) } |
コピーコンストラクタの定義では,どのようにオブジェクト間のコピーを行うかを記述します. 多くの場合,コピーコンストラクタ内ではメンバ変数のコピーを行います.
一例として,MyStringクラスのcppファイル内においてコピーコンストラクタを記述する場合を示します.
MyString::MyString(const MyString& ref) { // 定義部(メンバ変数のコピー方法等を記述する) } |
コピーコンストラクタの呼び出しは以下の場合に発生します.
MyString s1("t123456A"); // const char*を引数とするコンストラクタが呼ばれる MyString s2 = "t123456A"; // const char*を引数とするコンストラクタが呼ばれる MyString s3(s2); //ここはコピーコンストラクタが呼ばれる MyString s4 = s3; //ここもコピーコンストラクタが呼ばれる |
// 引数が値渡し void func(MyString str1, MyString str2) { ... } int main() { MyString s1("t123456A"); MyString s2; // func呼び出し時,str1とstr2のそれぞれでコピーコンストラクタが呼ばれる. // str1はs1をコピーすることで初期化され,str2はs2をコピーすることで初期化される. func(s1, s2); ... } |
// 値を戻す関数 MyString func(const char *chr) { MyString ret; ret.setString(chr); return ret; } int main() { MyString s1; // func呼び出し時,returnでコピーコンストラクタが呼ばれる. // retのコピーがs1に代入される. s1 = func("t123456A"); ... } |
逆に,以下の例ではコピーコンストラクタは呼ばれません.
MyString s1("t123456A"); MyString s2; s2 = s1; // ここでコピーコンストラクタは呼ばれない. |
// 引数が参照渡し void func(MyString& str1, MyString& str2) { ... } int main() { MyString s1("t123456A"); MyString s2; // func呼び出し時,s1とs2の参照が渡されるためコピーコンストラクタは呼ばれない. func(s1, s2); ... } |
MyString g_s; // 参照を戻す関数 MyString& func(const char *chr) { g_s.setString(chr); return g_s; } int main() { MyString s1; // func呼び出し時,returnは参照を返しているのでコピーコンストラクタは呼ばれない. s1 = func("t123456A"); ... } |
もし明示的にコピーコンストラクタを定義しない場合,コンパイラは自動的に単純なコピーによってオブジェクトの初期化を行います. よって,クラスによってはコピーコンストラクタを明示的に定義しない場合もありますが,クラスメンバにポインタを含む場合,単純なコピーでは問題が生じる場合があります.