参照渡しとかの個人的研究(やっぱりC++)

C++での参照渡しのまとめ。

たとえば、容量のでかいオブジェクトをそのまま引数に渡すと(コピー渡し)、
メモリの容量を余計食うようね!だから参照かポインタを渡そうね!!!
byビャーネたん(C++のママ)

…いや、参照渡し、ポインタ渡しどっちがいいの?????
てかどっちも同じじゃね????
と、思ったので個人的にまとめてみました。

では、引数でポインタを渡す関数の例を。

1
2
3
4
5
//Objectというクラス型のオブジェクトにメンバ(int)mがあるという想定。
//Object *ob;と宣言したらtest(ob);と使う。
void test(Object *ob){
ob->m = 5;
}

この関数では引数にアドレス(ポインタ)を渡す。
そして関数内でアロー演算子でメンバにアクセスして、値を見る(変更する)ことができる。

次に参照渡しバージョンをみましょう。

1
2
3
4
5
//Objectというクラス型のオブジェクトにメンバ(int)mがあるという想定。
//Object ob;と宣言したらtest(ob);と使う。
void test(Object &ob){
ob.m = 5;
}

この関数では引数に普通にオブジェクトを渡す。
そして関数内では.でメンバにアクセスして、値を見る(変更する)ことができる。

やってること結構同じじゃん!!!!!!!!!

ほぼ同じだけど、個人的には参照渡しのほうが見やすいよね!という感じですね。
ただ、自分が用意した「引数が参照渡しの関数」は他人から見ると、
引数が参照渡しかただのコピー渡しかわかんないので、
「引数オブジェクトの中身を変更されるされないがわかんないから危ないよねー」感ありますね。
(リファレンスを作れ!)

ただ、個人的な使い分けとしては値を変えるときにポインタ渡しで、
値を変えたくない時は参照渡しで、という考えです。
そういう時、参照渡しの時は、引数にconstつけてみたらいいかも。

1
2
3
4
5
//Objectというクラス型のオブジェクトにメンバ(int)mがあるという想定。
//Object ob;と宣言したらtest(ob);と使う。
void test(const Object &ob){//引数はこの関数内では、定数にする。
//ob.m = 5; 値を見ることしかできないのでこれだとコンパイルエラー。
}

じゃあ、いろいろ実験してみよう。
以下の様な複素数クラスを定義してみました。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//複素数クラス
class complex{
private:
double re,im;//reは実数部、imは虚数部。そのまま読むとreim(霊夢)。
public:
//コンストラクタ
complex(double a,double b):re{a},im{b}{
}
//最初にre、後にim、その後オブジェクト自身のアドレスを表示
void go(){
cout << re << " " << im << " " << this <<endl;
}
};

また、complex向けの+=演算子を作ります。
作り方としては、
a += b;
だと、aが代入先でbが引数となります。
機能としては、complex(複素数)同士で足し算するやつです。
ここでは右辺部をコピー渡しで行きます。

1
2
3
4
5
6
7
8
9
//class complexスコープ(public:)に追記
complex operator+=(const complex a){//コピー渡し
cout << "+= 実行" << endl;
cout << "左辺のオブジェクトのアドレスは" << this <<endl;//そのままの意味
cout << "右辺のオブジェクトのアドレスは" << &a <<endl; //そのままの意味
re += a.re;//実数部計算
im += a.im;//虚数部計算
return *this;
}

そんでもってmain関数は、

1
2
3
4
5
6
7
8
9
10
11
12
13
14
int main (void){
complex a{1.1,1.2};//1.1 + 1.2iという複素数
complex b{1.0,1.3};//1.0 + 1.3i
//a,bの複素数、それぞれのアドレス表示
a.go();
b.go();
a+=b;//bをコピー渡ししてaにa+bした結果を返す(この時、両辺値のアドレス表示)
//a,bの複素数、それぞれのアドレス表示
a.go();
b.go();
}

簡単にざっくり言うと、コピー渡しで渡していたオブジェクトは、コピーであることの確認。
んで結果は、

1
2
3
4
5
6
7
8
9
bash-3.2$ g++ -std=c++11 main2.cpp
bash-3.2$ ./a.out
1.1 1.2 0x7fff53280660
1 1.3 0x7fff53280650
+= 実行
左辺のオブジェクトのアドレスは0x7fff53280660
右辺のオブジェクトのアドレスは0x7fff532805d0
2.1 2.5 0x7fff53280660
1 1.3 0x7fff53280650

結果から、宣言したオブジェクトのアドレス(4行目)と計算内部のアドレス(右辺のアドレス)が違うのがわかりますね。
コピー渡しをすると、別のアドレスにオブジェクトがコピーされることがわかりますね。

次に、complex向けの+=演算子を変更。
ここでは右辺部を参照渡しで行きます。

1
2
3
4
5
6
7
8
9
//class complexスコープ(public:)に追記
complex operator+=(const complex &a){//コピー渡し
cout << "+= 実行" << endl;
cout << "左辺のオブジェクトのアドレスは" << this <<endl;//そのままの意味
cout << "右辺のオブジェクトのアドレスは" << &a <<endl; //そのままの意味
re += a.re;//実数部計算
im += a.im;//虚数部計算
return *this;
}

結果は、

1
2
3
4
5
6
7
8
9
bash-3.2$ g++ -std=c++11 main2.cpp
bash-3.2$ ./a.out
1.1 1.2 0x7fff53bd4660
1 1.3 0x7fff53bd4650
+= 実行
左辺のオブジェクトのアドレスは0x7fff53bd4660
右辺のオブジェクトのアドレスは0x7fff53bd4650
2.1 2.5 0x7fff53bd4660
1 1.3 0x7fff53bd4650

結果から、宣言したオブジェクトのアドレス(4行目)と計算内部のアドレス(右辺のアドレス)が同じなのがわかりますね。
これから、参照渡しをすると、オブジェクトがコピーされないがわかりますね。

+=を元に、+計算を作りましょう。
使うときは、a = c + b;となるため、c,bが引数です。

1
2
3
4
//これをcomplexのメンバにすると怒られるので、complexスコープ外へ
complex operator+(const complex &c,const complex &d){
return c+=d;//+=した後のcを返す。
}

しかし、これだとコンパイルエラーを吐きます。
なぜならば、ここでの+=演算子はcを変更します、が、
引数上、cは定数です。なので、定数を変えることはできません。
また、constを外すだけだと、cも変わっちゃいますよね…。
ということは、cはコピー渡しでないといけません。
(complexくらいサイズ小さいから大丈夫だけど、サイズの大きいオブジェクトが来ると困る)
ココらへんの解決策の話だと、これまた別の「コピー代入」の話になりますが、それはまた今度。

というわけで修正

1
2
3
4
//これをcomplexのメンバにすると怒られるので、complexスコープ外へ
complex operator+(complex c,const complex &d){
return c+=d;//+=した後のcを返す。
}

メインはこうします。

1
2
3
4
5
6
7
8
9
10
11
int main (void){
complex a{1.1,1.2};
complex b{1.0,1.3};
a.go();
b.go();
a = b + a;
a.go();
b.go();
}

さて、動作させてみましょう。

1
2
3
4
5
6
7
8
9
bash-3.2$ g++ -std=c++11 main2.cpp
bash-3.2$ ./a.out
1.1 1.2 0x7fff54cb7660
1 1.3 0x7fff54cb7650
+= 実行
左辺のオブジェクトのアドレスは0x7fff54cb7600
右辺のオブジェクトのアドレスは0x7fff54cb7660
2.1 2.5 0x7fff54cb7660
1 1.3 0x7fff54cb7650

+=起動メッセージが出てるのは、+のとき+=使ってるからです。
a = b(実行結果で言う左辺) + a(実行結果で言う右辺)と、でも考えてください。
途中コピー渡しになっちゃってますが(4行目のアドレス≠左辺のアドレス(コピー))
bは変わらなかったので良しとしましょう。

…話にオチがないのですが、これで終わりです。