はじめに
変数の生成と代入にあたって、呼ばれる関数(コンストラクタ・コピーコンストラクタ・代入演算子)とタイミングの違いについて取り上げていきます。
違いを理解するにあたって、コンストラクタとコピーコンストラクタと代入演算子は切っても切り離せない関係です。
自作クラスの生成の話を始める前に、まず、身近に使っているint型を例に挙げると…
変数を使う方法 ~2通り~
代入
1 2 | int hoge; //変数生成 hoge = 5; //代入 |
変数を宣言(生成)した後に値を代入している。これが代入
初期化
1 | int hoge = 5; //生成と代入が同時 |
のように変数の宣言(生成)と同時に値を代入する。これが初期化
注)「代入する」と言ったが、宣言と同時の場合、初期化という意味に変わることに注意。
また、実は、以下のような書き方もC++ではでき、先程と同じ意味。
C言語ではできなかったが…
1 | int hoge(5); |
代入タイミングが宣言と同時であることには変わりはありません。
代入のタイミングが違う!
このタイミングの違いによって、その都度内部で呼ばれる関数が違うというのが、今から話す内容です。
呼ばれる関数の違い
呼ばれる関数である、コンストラクタ・コピーコンストラクタ・代入演算子のオーバーロードを以下の通り定義した。
注)普段これらは、自分から定義しなければ自動で定義される。
num 変数は代入演算子オーバーロード関数内の計算用。
これで、タイミングを調べていく。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | using namespace std; class Obj { public: Obj(int n) { num = n; cout << "コンストラクタが呼ばれたよ" << endl; } // コンストラクタ Obj(const Obj &obj) { num = obj.num; cout << "コピーコンストラクタが呼ばれたよ" << endl; } // コピーコンストラクタ Obj& operator=(const Obj& v) { num = v.num; cout << "代入演算子が呼ばれたよ" << endl; return *this; };// =演算子のオーバーロード int num = 0; }; |
コンストラクタ呼ばれるタイミング
初期化したとき ~2通り~
ここでは、コンストラクタの引数は int型なので、int型で初期化したとき。
デフォルトコンストラクタであれば、引数なしのとき呼ばれます。
次項目で説明しますが、ここでの引数は自分の型と同じではないことが重要です。
普通に初期化したとき
1 | Obj obj(3); |
new 演算子で新たに生成したとき
new 演算子を使った右辺の段階で生成されています。
1 | Obj* obj = new Obj(3); |
注)左辺への代入は、ポインタの指し示す矢印を決めただけで、生成には関与しないことに注意。
コピーコンストラクタが呼ばれるタイミング
コピーコンストラクタとは、一言でいうと
コンストラクタの引数を自分と同じ型にしたバージョン
つまり、私たちが普段作る、いろんな引数をとるコンストラクタのバリエーションの一種にすぎないのです。
仮引数のconstや&は、参照渡しによる速度向上が目的なので、本質ではないです。ただ、付けないとコピーコンストラクタだと認めてもらえないので、付けましょう。
自分の型と 同じクラス (または 派生クラス) の引数でもって初期化したとき ~2通り~
ここで、自分は Obj型なので、Obj型で初期化したときです。
普通に初期化したとき
1 2 | Obj obj1(2); Obj obj2 = obj1; //初期化 ここでコピーコンストラクタ呼ばれる |
「はじめに」の項目で説明した通り、2行目は
1 | Obj obj2(obj1); //初期化 ここでコピーコンストラクタ呼ばれる |
のようにも、記述できます。
関数の仮引数に渡すとき
1 2 3 4 5 6 | void func(Obj f_obj) {} //仮引数の f_obj が初期化 int main() { Obj obj; func(obj); //ここでコピーコンストラクタ呼ばれる } |
関数内の仮引数はスコープに従って、その中、限定の変数が作られるのでした。
つまり、
Obj f_obj = obj;
という初期化が仮引数に渡す際 func関数内で行われているのです。
ちゃんと、同じ型で初期化されていますね。
代入演算子のオーバーロードが呼ばれるタイミング
代入したとき
注)「はじめに」の項目で説明した通り、代入と初期化は違う!
1 2 3 | Obj obj1(2); Obj obj2(5); obj2 = obj1; //ここで代入演算子のオーバーロードした関数が呼ばれる |
普通に関数名で呼び出したとき
実は、3行目は
1 | obj2.operator=(obj1); //ここで代入演算子のオーバーロードした関数が呼ばれる |
のように置き換えることができます。
=演算子の代入は、この関数名を呼び出す手間を省いてくれていたんですね。
まとめ
コピーコンストラクタはコンストラクタの一種
引数の違い
コピーコンストラクタ・・・自分と同じ型(1つだけ引数とる)
コンストラクタ・・・コピーコンストラクタの引数条件以外
コンストラクタは初期化時に呼ばれる
初期化とは 宣言と代入が同じタイミング
代入時の代入(=)演算子はoperator関数の呼び出しを省略していた
参考
コピーコンストラクタって? – Qiita:https://qiita.com/youthini/items/2314ad7cf498e5f199d7
書籍「Effective C++ 第3版」